Вопрос: Как клонировать или копировать список?


Каковы варианты клонирования или копирования списка в Python?

С помощью new_list = my_listзатем изменяет new_listкаждый раз my_listизменения.
Почему это?


1634


источник


Ответы:


С new_list = my_list, у вас фактически нет двух списков. Назначение просто копирует ссылку на список, а не фактический список, поэтому оба new_listа также my_listобратитесь к тому же списку после назначения.

Чтобы на самом деле скопировать список, у вас есть различные возможности:

  • Вы можете отрезать его:

    new_list = old_list[:]
    

    Алекс Мартелли мнение (по крайней мере еще в 2007 году ) об этом, что это странный синтаксис, и нет смысла использовать его когда-либо , ;) (По его мнению, следующий - более читаемый).

  • Вы можете использовать встроенный list()функция:

    new_list = list(old_list)
    
  • Вы можете использовать общие copy.copy():

    import copy
    new_list = copy.copy(old_list)
    

    Это немного медленнее, чем list()потому что он должен определить тип данных old_listпервый.

  • Если список содержит объекты, и вы хотите их скопировать, используйте общие copy.deepcopy():

    import copy
    new_list = copy.deepcopy(old_list)
    

    Очевидно, самый медленный и самый необходимый для памяти способ, но иногда неизбежный.

Пример:

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

Результат:

original: ['foo', 5, 'baz']
slice: ['foo', 5]
list(): ['foo', 5]
copy: ['foo', 5]
deepcopy: ['foo', 1]

2262



Феликс уже дал отличный ответ, но я думал, что сделаю сравнение скорости различных методов:

  1. 10,59 сек (105,9 ед / мин) - copy.deepcopy(old_list)
  2. 10.16 сек (101.6us / itn) - чистый питон Copy()метод копирования классов с помощью deepcopy
  3. 1.488 сек (14.88us / itn) - чистый питон Copy()метод не копирует классы (только dicts / lists / tuples)
  4. 0,325 с (3,25 ед / дюйм) - for item in old_list: new_list.append(item)
  5. 0,217 с (2,17 ед / дюйм) - [i for i in old_list]список )
  6. 0,186 с (1,86 ед / мин) - copy.copy(old_list)
  7. 0,075 с (0,75 ед / дюйм) - list(old_list)
  8. 0,053 с (0,53 ед / мин) - new_list = []; new_list.extend(old_list)
  9. 0,039 с (0,39 г / дюйм) - old_list[:]( нарезка списка )

Таким образом, самая быстрая нарезка списка. Но имейте в виду, что copy.copy(), list[:]а также list(list), В отличие от copy.deepcopy()и версия python не копирует списки, словари и экземпляры классов в списке, поэтому, если оригиналы меняются, они также будут изменены в скопированном списке и наоборот.

(Вот сценарий, если кто-то заинтересован или хочет поднять какие-либо вопросы :)

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

РЕДАКТИРОВАТЬ : Добавлены классы стиля старого стиля, а также диктофоны в тесты и сделали версию python намного быстрее и добавили еще несколько методов, включая выражения списков и extend(),


441



Я было сказано что Python 3.3+ добавляет list.copy()метод, который должен быть таким же быстрым, как нарезка:

newlist = old_list.copy()


116



Каковы варианты клонирования или копирования списка в Python?

В Python 3 можно сделать мелкую копию с помощью:

a_copy = a_list.copy()

В Python 2 и 3 вы можете получить мелкую копию с полным фрагментом оригинала:

a_copy = a_list[:]

объяснение

Существует два семантических способа копирования списка. Неглубокая копия создает новый список тех же объектов, в глубокой копии создается новый список, содержащий новые эквивалентные объекты.

Неверная копия списка

Неглубокая копия копирует только сам список, который является контейнером ссылок на объекты в списке. Если объекты, содержащиеся в них, являются изменяемыми и один из них изменен, это изменение будет отражено в обоих списках.

В Python 2 и 3. есть разные способы сделать это. Пути Python 2 также будут работать в Python 3.

Python 2

В Python 2 идиоматический способ создания мелкой копии списка состоит из полного фрагмента оригинала:

a_copy = a_list[:]

Вы также можете выполнить одно и то же, передав список через конструктор списка,

a_copy = list(a_list)

но использование конструктора менее эффективно:

>>> timeit
>>> l = range(20)
>>> min(timeit.repeat(lambda: l[:]))
0.30504298210144043
>>> min(timeit.repeat(lambda: list(l)))
0.40698814392089844

Python 3

В Python 3 списки получают list.copyметод:

a_copy = a_list.copy()

В Python 3.5:

>>> import timeit
>>> l = list(range(20))
>>> min(timeit.repeat(lambda: l[:]))
0.38448613602668047
>>> min(timeit.repeat(lambda: list(l)))
0.6309100328944623
>>> min(timeit.repeat(lambda: l.copy()))
0.38122922903858125

Создание другого указателя не сделать копию

Использование new_list = my_list затем изменяет new_list каждый раз, когда изменяется my_list. Почему это?

my_listэто просто имя, которое указывает на фактический список в памяти. Когда ты говоришь new_list = my_listвы не делаете копию, вы просто добавляете другое имя, указывающее на этот исходный список в памяти. У нас могут быть подобные проблемы, когда мы делаем копии списков.

>>> l = [[], [], []]
>>> l_copy = l[:]
>>> l_copy
[[], [], []]
>>> l_copy[0].append('foo')
>>> l_copy
[['foo'], [], []]
>>> l
[['foo'], [], []]

Список - это всего лишь массив указателей на содержимое, поэтому мелкая копия просто копирует указатели, поэтому у вас есть два разных списка, но они имеют одинаковое содержимое. Чтобы сделать копии содержимого, вам нужна глубокая копия.

Глубокие копии

Сделать глубокая копия списка, в Python 2 или 3, используйте deepcopyв copyмодуль :

import copy
a_deep_copy = copy.deepcopy(a_list)

Чтобы продемонстрировать, как это позволяет нам создавать новые под-списки:

>>> import copy
>>> l
[['foo'], [], []]
>>> l_deep_copy = copy.deepcopy(l)
>>> l_deep_copy[0].pop()
'foo'
>>> l_deep_copy
[[], [], []]
>>> l
[['foo'], [], []]

И поэтому мы видим, что глубокий скопированный список - это совсем другой список из оригинала. Вы можете перевернуть свою собственную функцию, но не надо. Вероятно, вы создадите ошибки, которых иначе не было бы, используя функцию глубокой копии стандартной библиотеки.

Не использовать eval

Вы можете видеть, что это используется как способ для глубокой печати, но не делайте этого:

problematic_deep_copy = eval(repr(a_list))
  1. Это опасно, особенно если вы оцениваете что-то из источника, которому вы не доверяете.
  2. Это ненадежно, если подэлемент, который вы копируете, не имеет представления, которое может быть доказано для воспроизведения эквивалентного элемента.
  3. Он также менее эффективен.

В 64-битном Python 2.7:

>>> import timeit
>>> import copy
>>> l = range(10)
>>> min(timeit.repeat(lambda: copy.deepcopy(l)))
27.55826997756958
>>> min(timeit.repeat(lambda: eval(repr(l))))
29.04534101486206

на 64-битном Python 3.5:

>>> import timeit
>>> import copy
>>> l = list(range(10))
>>> min(timeit.repeat(lambda: copy.deepcopy(l)))
16.84255409205798
>>> min(timeit.repeat(lambda: eval(repr(l))))
34.813894678023644

83



Уже есть много ответов, которые расскажут вам, как сделать правильную копию, но никто из них не говорит, почему ваша оригинальная «копия» не удалась.

Python не сохраняет значения в переменных; он связывает имена с объектами. Ваше первоначальное назначение заняло объект, на который ссылается my_listи связал его с new_listтакже. Независимо от того, какое имя вы используете, остается только один список, поэтому изменения, сделанные при обращении к нему как my_listбудет сохраняться при обращении к нему как new_list, Каждый из ответов на этот вопрос дает вам различные способы создания нового объекта для привязки к new_list,

Каждый элемент списка действует как имя, поскольку каждый элемент связывается не исключительно с объектом. Неглубокая копия создает новый список, элементы которого привязываются к тем же объектам, что и раньше.

new_list = list(my_list)  # or my_list[:], but I prefer this syntax
# is simply a shorter way of:
new_list = [element for element in my_list]

Чтобы сделать копию списка еще одним шагом, скопируйте каждый объект, к которому относится ваш список, и привяжите эти копии элементов к новому списку.

import copy  
# each element must have __copy__ defined for this...
new_list = [copy.copy(element) for element in my_list]

Это еще не глубокая копия, потому что каждый элемент списка может ссылаться на другие объекты, точно так же, как список привязан к его элементам. Чтобы рекурсивно скопировать каждый элемент в списке, а затем каждый другой объект, на который ссылаются каждый элемент, и т. Д .: выполните глубокую копию.

import copy
# each element must have __deepcopy__ defined for this...
new_list = copy.deepcopy(my_list)

Видеть документация для получения дополнительной информации об угловых случаях при копировании.


39



new_list = list(old_list)


30



использование thing[:]

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

26



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


26



All of the other contributors gave great answers, which work when you have a single dimension (leveled) list, however of the methods mentioned so far, only copy.deepcopy() works to clone/copy a list and not have it point to the nested list objects when you are working with multidimensional, nested lists (list of lists). While Felix Kling refers to it in his answer, there is a little bit more to the issue and possibly a workaround using built-ins that might prove a faster alternative to deepcopy.

While new_list = old_list[:], copy.copy(old_list)' and for Py3k old_list.copy() work for single-leveled lists, they revert to pointing at the list objects nested within the old_list and the new_list, and changes to one of the list objects are perpetuated in the other.

Edit: New information brought to light

As was pointed out by both Aaron Hall and PM 2Ring using eval() is not only a bad idea, it is also much slower than copy.deepcopy().

This means that for multidimensional lists, the only option is copy.deepcopy(). With that being said, it really isn't an option as the performance goes way south when you try to use it on a moderately sized multidimensional array. I tried to timeit using a 42x42 array, not unheard of or even that large for bioinformatics applications, and I gave up on waiting for a response and just started typing my edit to this post.

It would seem that the only real option then is to initialize multiple lists and work on them independently. If anyone has any other suggestions, for how to handle multidimensional list copying, it would be appreciated.

As others have stated, there can be are significant performance issues using the copy module and copy.deepcopy for multidimensional lists. Trying to work out a different way of copying the multidimensional list without using deepcopy, (I was working on a problem for a course that only allows 5 seconds for the entire algorithm to run in order to receive credit), I came up with a way of using built-in functions to make a copy of the nested list without having them point at one another or at the list objects nested within them. I used eval() and repr() in the assignment to make the copy of the old list into the new list without creating a link to the old list. It takes the form of:

new_list = eval(repr(old_list))

Basically what this does is make a representation of old_list as a string and then evaluates the string as if it were the object that the string represents. By doing this, no link to the original list object is made. A new list object is created and each variable points to its own independent object. Here is an example using a 2 dimensional nested list.

old_list = [[0 for j in range(y)] for i in range(x)] # initialize (x,y) nested list

# assign a copy of old_list to new list without them pointing to the same list object
new_list = eval(repr(old_list)) 

# make a change to new_list 
for j in range(y):
    for i in range(x):
    new_list[i][j] += 1

If you then check the contents of each list, for example a 4 by 3 list, Python will return

>>> new_list

[[1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1]]

>>> old_list

[[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]]

While this probably isn't the canonical or syntactically correct way to do it, it seems to work well. I haven't tested performance, but I am going to guess that eval() and rep() will have less overhead to run than deepcopy will.


18



Python 3.6.0 Timings

Here are the timing results using Python 3.6.0. Keep in mind these times are relative to one another, not absolute.

I stuck to only doing shallow copies, and also added some new methods that weren't possible in Python2, such as list.copy() (the Python3 slice equivalent) and list unpacking (*new_list, = list):

METHOD                  TIME TAKEN
b = a[:]                6.468942025996512   #Python2 winner
b = a.copy()            6.986593422974693   #Python3 "slice equivalent"
b = []; b.extend(a)     7.309216841997113
b = a[0:len(a)]         10.916740721993847
*b, = a                 11.046738261007704
b = list(a)             11.761539687984623
b = [i for i in a]      24.66165203397395
b = copy.copy(a)        30.853400873980718
b = []
for item in a:
  b.append(item)        48.19176080400939

We can see the old winner still comes out on top, but not really by a huge amount, considering the increased readability of the Python3 list.copy() approach.

Note that these methods do not output equivalent results for any input other than lists. They all work for sliceable objects, a few work for any iterable, but only copy.copy() works for any Python object.


Here is the testing code for interested parties (Template from here):

import timeit

COUNT = 50000000
print("Array duplicating. Tests run", COUNT, "times")
setup = 'a = [0,1,2,3,4,5,6,7,8,9]; import copy'

print("b = list(a)\t\t", timeit.timeit(stmt='b = list(a)', setup=setup, number=COUNT))
print("b = copy.copy(a)\t\t", timeit.timeit(stmt='b = copy.copy(a)', setup=setup, number=COUNT))
print("b = a.copy()\t\t", timeit.timeit(stmt='b = a.copy()', setup=setup, number=COUNT))
print("b = a[:]\t\t", timeit.timeit(stmt='b = a[:]', setup=setup, number=COUNT))
print("b = a[0:len(a)]\t", timeit.timeit(stmt='b = a[0:len(a)]', setup=setup, number=COUNT))
print("*b, = a\t", timeit.timeit(stmt='*b, = a', setup=setup, number=COUNT))
print("b = []; b.extend(a)\t", timeit.timeit(stmt='b = []; b.extend(a)', setup=setup, number=COUNT))
print("b = []\nfor item in a: b.append(item)\t", timeit.timeit(stmt='b = []\nfor item in a:  b.append(item)', setup=setup, number=COUNT))
print("b = [i for i in a]\t", timeit.timeit(stmt='b = [i for i in a]', setup=setup, number=COUNT))

12



Unlike other languages that have variable and value, Python has name and object.

This statement:

a = [1,2,3]

means to give the list (object) a name a, and, this:

b = a

just gives the same object a a new name b, so whenever you do something with a, the object changes and therefore b changes.

The only way to make a really copy of a is to create a new object like other answers already have said.

You can see more about this here.


11