tags:

views:

577

answers:

5

Hello,

Java has cloning methods. How can I do it on a list in python?

+3  A: 

Use thing[:]

>>> a = [1,2]
>>> b = a[:]
>>> a += [3]
>>> a
[1, 2, 3]
>>> b
[1, 2]
>>> 
Paul Tarjan
+2  A: 

Python's idiom for doing this is newList = oldList[:]

erisco
+30  A: 

You have various possibilities:

  • 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 copy
    new_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 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 str(self.val)

foo = Foo(1)

a = ['foo', foo]
b = a[:]
c = list(a)
d = copy.copy(a)
e = copy.deepcopy(a)

# edit orignal list and instance 
a.append('baz')
foo.val = 5

print "original: %r\n slice: %r\n list(): %r\n copy: %r\n deepcopy: %r" \
       % (a, b, c, d, e)

Result:

original: ['foo', 5, 'baz']
slice: ['foo', 5]
list(): ['foo', 5]
copy: ['foo', 5]
deepcopy: ['foo', 1]
Felix Kling
+1 for being comprehensive.
tzaman
+1 for 3 options, you can add new_list = list(new_list) as 4th
Anurag Uniyal
@Anurag Uniyal: Oh thank you, I totally forgot about that ;) I added it...
Felix Kling
+1 for treating shallow as well as deep copies.
Thomas
Classes should inherit `object` rather than nothing so that you are using new-style classes, i.e. `class Foo(object):`
Mike Graham
`+= ['baz']` seems like an odd way to write `.append('baz')`.
Mike Graham
@Mike Graham: I wouldn't write this in production code but here I just wanted to illustrate the effects of various copy commands and I think it really doesn't matter whether my class is new style or not ;)
Felix Kling
@Felix, of course it doesn't change the effect you see here, but every time there's a needlessly suboptimal snippets posted here, there is a chance readers might come along and mimic them.
Mike Graham
@Mike Graham: Mmh thats true. Ok then, I provide more optimal code ;)
Felix Kling
+3  A: 

new_list = list(old_list)

+9  A: 

Felix already provided an excellent answer, but I thought I'd do a speed comparison of the various methods:

  1. new_list = copy.deepcopy(old_list): 10.5920000076
  2. pure python Copy() method copying classes with deepcopy: 10.1569998264
  3. pure python Copy() method not copying classes (only dicts/lists/tuples): 1.4880001545
  4. for item in old_list: new_list.append(item): 0.325999975204
  5. new_list = [i for i in old_list] (a list comprehension): 0.217000007629
  6. new_list = copy.copy(old_list): 0.186999797821
  7. new_list = list(old_list): 0.0750000476837
  8. new_list = []; new_list.extend(old_list): 0.0529999732971
  9. new_list = old_list[:] (list slicing): 0.0390000343323

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 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 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 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

EDIT: Added new-style, old-style classes and dicts to the benchmarks, and made the python version much faster and added some more methods including list expressions and extend().

David Morrissey
+1 I like it :) But I think `deepcopy` performs even worse if you have *real* (in a sense of not being a string) objects in the list...
Felix Kling
I've added new/old type classes/dicts, thanks for the feedback
David Morrissey
+1: Surprising thing is that `new_list = []; new_list.extend(L)` is faster than `new_list = list(L)`.
J.F. Sebastian
@JFSebastian: Maybe because `list()` takes also other types besides lists. (I think any iteratable). Maybe it does some type checking...
Felix Kling
@Felix: `list.extend()` also accepts any iterable, so it shouldn't be type checking.
J.F. Sebastian
@JFSebastian: I think `extend()` must do type checking. I didn't add it to the list because I didn't think there was a point but using a generator expression with `new_list.extend()` (i.e. `new_list.extend(i for i in old_list)` was 0.43 seconds.
David Morrissey