List changes unexpectedly after assignment. Why is this and how can I prevent it? List changes unexpectedly after assignment. Why is this and how can I prevent it? python python

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 of old_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:

  1. 10.59 sec (105.9 µs/itn) - copy.deepcopy(old_list)
  2. 10.16 sec (101.6 µs/itn) - pure Python Copy() method copying classes with deepcopy
  3. 1.488 sec (14.88 µs/itn) - pure Python Copy() method not copying classes (only dicts/lists/tuples)
  4. 0.325 sec (3.25 µs/itn) - for item in old_list: new_list.append(item)
  5. 0.217 sec (2.17 µs/itn) - [i for i in old_list] (a list comprehension)
  6. 0.186 sec (1.86 µs/itn) - copy.copy(old_list)
  7. 0.075 sec (0.75 µs/itn) - list(old_list)
  8. 0.053 sec (0.53 µs/itn) - new_list = []; new_list.extend(old_list)
  9. 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


I've been told that Python 3.3+ adds the list.copy() method, which should be as fast as slicing:

newlist = old_list.copy()