python – List changes unexpectedly after assignment. Why is this and how can I prevent it?
python – List changes unexpectedly after assignment. Why is this and how can I prevent it?
With new_list = my_list
, you dont 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 Martellis 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 copy new_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 copy new_list = copy.deepcopy(old_list)
Obviously the slowest and most memory-needing method, but sometimes unavoidable.
Example:
import copy
class 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 = 5
print(original: %rnlist.copy(): %rnslice: %rnlist(): %rncopy: %rndeepcopy: %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 Id 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 dont 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.
(Heres the script if anyones interested or wants to raise any issues:)
from copy import deepcopy
class 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 arent 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?
# Its immutable, so ignore it!
pass
elif use_deepcopy:
obj = deepcopy(obj)
return obj
if __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
python – List changes unexpectedly after assignment. Why is this and how can I prevent it?
Ive been told that Python 3.3+ adds the list.copy()
method, which should be as fast as slicing:
Also Read: python – List changes unexpectedly after assignment. Why is this and how can I prevent it?
newlist = old_list.copy()