List changes unexpectedly after assignment. Why is this and how can I prevent it?
With new_list = my_list
, you don't actually have two lists. The assignment just copies the reference to the list, not the actual list, so both new_list
and my_list
refer to the same list after the assignment.
To actually copy the list, you have various possibilities:
You can use the builtin
list.copy()
method (available since Python 3.3):new_list = old_list.copy()
You can slice it:
new_list = old_list[:]
Alex Martelli's opinion (at least back in 2007) about this is, that it is a weird syntax and it does not make sense to use it ever. ;) (In his opinion, the next one is more readable).
You can use the built in
list()
function:new_list = list(old_list)
You can use generic
copy.copy()
:import copynew_list = copy.copy(old_list)
This is a little slower than
list()
because it has to find out the datatype ofold_list
first.If the list contains objects and you want to copy them as well, use generic
copy.deepcopy()
:import copynew_list = copy.deepcopy(old_list)
Obviously the slowest and most memory-needing method, but sometimes unavoidable.
Example:
import copyclass Foo(object): def __init__(self, val): self.val = val def __repr__(self): return 'Foo({!r})'.format(self.val)foo = Foo(1)a = ['foo', foo]b = a.copy()c = a[:]d = list(a)e = copy.copy(a)f = copy.deepcopy(a)# edit orignal list and instance a.append('baz')foo.val = 5print('original: %r\nlist.copy(): %r\nslice: %r\nlist(): %r\ncopy: %r\ndeepcopy: %r' % (a, b, c, d, e, f))
Result:
original: ['foo', Foo(5), 'baz']list.copy(): ['foo', Foo(5)]slice: ['foo', Foo(5)]list(): ['foo', Foo(5)]copy: ['foo', Foo(5)]deepcopy: ['foo', Foo(1)]
Felix already provided an excellent answer, but I thought I'd do a speed comparison of the various methods:
- 10.59 sec (105.9 µs/itn) -
copy.deepcopy(old_list)
- 10.16 sec (101.6 µs/itn) - pure Python
Copy()
method copying classes with deepcopy - 1.488 sec (14.88 µs/itn) - pure Python
Copy()
method not copying classes (only dicts/lists/tuples) - 0.325 sec (3.25 µs/itn) -
for item in old_list: new_list.append(item)
- 0.217 sec (2.17 µs/itn) -
[i for i in old_list]
(a list comprehension) - 0.186 sec (1.86 µs/itn) -
copy.copy(old_list)
- 0.075 sec (0.75 µs/itn) -
list(old_list)
- 0.053 sec (0.53 µs/itn) -
new_list = []; new_list.extend(old_list)
- 0.039 sec (0.39 µs/itn) -
old_list[:]
(list slicing)
So the fastest is list slicing. But be aware that copy.copy()
, list[:]
and list(list)
, unlike copy.deepcopy()
and the python version don't copy any lists, dictionaries and class instances in the list, so if the originals change, they will change in the copied list too and vice versa.
(Here's the script if anyone's interested or wants to raise any issues:)
from copy import deepcopyclass old_class: def __init__(self): self.blah = 'blah'class new_class(object): def __init__(self): self.blah = 'blah'dignore = {str: None, unicode: None, int: None, type(None): None}def Copy(obj, use_deepcopy=True): t = type(obj) if t in (list, tuple): if t == tuple: # Convert to a list if a tuple to # allow assigning to when copying is_tuple = True obj = list(obj) else: # Otherwise just do a quick slice copy obj = obj[:] is_tuple = False # Copy each item recursively for x in xrange(len(obj)): if type(obj[x]) in dignore: continue obj[x] = Copy(obj[x], use_deepcopy) if is_tuple: # Convert back into a tuple again obj = tuple(obj) elif t == dict: # Use the fast shallow dict copy() method and copy any # values which aren't immutable (like lists, dicts etc) obj = obj.copy() for k in obj: if type(obj[k]) in dignore: continue obj[k] = Copy(obj[k], use_deepcopy) elif t in dignore: # Numeric or string/unicode? # It's immutable, so ignore it! pass elif use_deepcopy: obj = deepcopy(obj) return objif __name__ == '__main__': import copy from time import time num_times = 100000 L = [None, 'blah', 1, 543.4532, ['foo'], ('bar',), {'blah': 'blah'}, old_class(), new_class()] t = time() for i in xrange(num_times): Copy(L) print 'Custom Copy:', time()-t t = time() for i in xrange(num_times): Copy(L, use_deepcopy=False) print 'Custom Copy Only Copying Lists/Tuples/Dicts (no classes):', time()-t t = time() for i in xrange(num_times): copy.copy(L) print 'copy.copy:', time()-t t = time() for i in xrange(num_times): copy.deepcopy(L) print 'copy.deepcopy:', time()-t t = time() for i in xrange(num_times): L[:] print 'list slicing [:]:', time()-t t = time() for i in xrange(num_times): list(L) print 'list(L):', time()-t t = time() for i in xrange(num_times): [i for i in L] print 'list expression(L):', time()-t t = time() for i in xrange(num_times): a = [] a.extend(L) print 'list extend:', time()-t t = time() for i in xrange(num_times): a = [] for y in L: a.append(y) print 'list append:', time()-t t = time() for i in xrange(num_times): a = [] a.extend(i for i in L) print 'generator expression extend:', time()-t