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


2553

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

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

Ответы:


3332

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

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

  • Вы можете использовать встроенный list.copy()метод (доступен начиная с Python 3.3):

    new_list = old_list.copy()
  • Вы можете нарезать это:

    new_list = old_list[:]

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

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

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

    import copy
    new_list = copy.copy(old_list)

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

  • Если список содержит объекты и вы хотите скопировать их, используйте generic 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 '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: %r\nlist.copy(): %r\nslice: %r\nlist(): %r\ncopy: %r\ndeepcopy: %r'
      % (a, b, c, d, e, f))

Результат:

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

7
Если я не ошибаюсь: newlist = [*mylist]тоже есть возможность в Python 3. newlist = list(mylist)Может быть, это более понятно.
Стефан

9
другая возможность - new_list = old_list * 1
aris

4
Какие из этих методов являются мелким копированием, а какие - глубоким копированием?
Ишвар

4
@ Эсвар: все, кроме последнего, делают мелкую копию
Феликс Клинг

3
@ Ишвар это мелкая копия.
juanpa.arrivillaga

604

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

  1. 10,59 с (105,9us / itn) - copy.deepcopy(old_list)
  2. 10,16 с (101,6us / ITN) - чистый питон Copy() метод копирующий классы с глубокой копией
  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,17us / itn) - [i for i in old_list]( понимание списка )
  6. 0,186 с (1,86US / ITN) - copy.copy(old_list)
  7. 0,075 с (0,75 мкс / миль) - list(old_list)
  8. 0,053 с (0,53us / itn) - new_list = []; new_list.extend(old_list)
  9. 0,039 с (0,39us / itn) - old_list[:]( нарезка списка )

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

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

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

9
Поскольку вы проводите сравнительный анализ, может быть полезно включить контрольную точку. Являются ли эти цифры все еще точными в 2017 году с использованием Python 3.6 с полностью скомпилированным кодом? Я отмечаю ответ ниже ( stackoverflow.com/a/17810305/26219 ) уже ставит под сомнение этот ответ.
Марк Эдингтон

4
использовать timeitмодуль. Кроме того, вы не можете сделать много выводов из произвольных микро тестов, как это.
Кори Голдберг

3
Если вы хотите включить новую опцию для 3.5+, она [*old_list]должна быть примерно эквивалентна list(old_list), но, поскольку это синтаксис, а не общие пути вызова функций, это сэкономит немного времени выполнения (и в отличие от того old_list[:], что не приводит к типу convert, [*old_list]работает на любом итерируемом и выдает а list).
ShadowRanger

3
@CoreyGoldberg для немного менее произвольной микро-теста (использование timeit50м работает вместо 100k) см stackoverflow.com/a/43220129/3745896
река

1
@ShadowRanger на [*old_list]самом деле, кажется, превосходит практически любой другой метод. (см. мой ответ в предыдущих комментариях)
Река

151

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

newlist = old_list.copy()


6
Да, и в соответствии с DOCS docs.python.org/3/library/stdtypes.html#mutable-sequence-types , s.copy()создает неполную копию s(такой же , как s[:]).
CyberMew

На самом деле, кажется , что в настоящее время python3.8, .copy()это немного быстрее , чем нарезка. Смотрите ниже @AaronsHall ответ.
love.by.Иисус

126

Какие есть варианты для клонирования или копирования списка в 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'], [], []]

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

Не использовать 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

1
Вам не нужна глубокая копия, если список 2D. Если это список списков, и у этих списков нет списков внутри них, вы можете использовать цикл for. В настоящее время я использую, list_copy=[] for item in list: list_copy.append(copy(item))и это намного быстрее.
Джон Локк

54

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

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)

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



35

Давайте начнем с самого начала и исследуем этот вопрос.

Итак, предположим, у вас есть два списка:

list_1=['01','98']
list_2=[['01','98']]

И мы должны скопировать оба списка, теперь начиная с первого списка:

Итак, сначала давайте попробуем установить переменную copyв наш оригинальный список list_1:

copy=list_1

Теперь, если вы думаете, скопировать скопированный список_1, то вы не правы. idФункция может показать нам , если две переменные могут указывать на тот же объект. Давайте попробуем это:

print(id(copy))
print(id(list_1))

Выход:

4329485320
4329485320

Обе переменные являются одним и тем же аргументом. Вы удивлены?

Так как мы знаем, что python ничего не хранит в переменной, переменные просто ссылаются на объект, а объект хранит значение. Здесь объект, listно мы создали две ссылки на этот же объект с двумя разными именами переменных. Это означает, что обе переменные указывают на один и тот же объект, только с разными именами.

Когда вы делаете copy=list_1, это на самом деле делает:

введите описание изображения здесь

Здесь на изображении list_1 и copy два имени переменных, но объект одинаков для обеих переменных, которые list

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

copy[0]="modify"

print(copy)
print(list_1)

вывод:

['modify', '98']
['modify', '98']

Таким образом, он изменил оригинальный список:

Теперь давайте перейдем к питоническому методу копирования списков.

copy_1=list_1[:]

Этот метод исправляет первую проблему, которая у нас была:

print(id(copy_1))
print(id(list_1))

4338792136
4338791432

Таким образом, мы видим, что оба списка имеют разные идентификаторы, и это означает, что обе переменные указывают на разные объекты. Так что на самом деле здесь происходит:

введите описание изображения здесь

Теперь давайте попробуем изменить список и посмотрим, сталкиваемся ли мы с предыдущей проблемой:

copy_1[0]="modify"

print(list_1)
print(copy_1)

Выход:

['01', '98']
['modify', '98']

Как видите, он только изменил скопированный список. Это значит, что это сработало.

Как вы думаете, мы закончили? Давайте попробуем скопировать наш вложенный список.

copy_2=list_2[:]

list_2должен ссылаться на другой объект, который является копией list_2. Давайте проверим:

print(id((list_2)),id(copy_2))

Мы получаем вывод:

4330403592 4330403528

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

copy_2[0][1]="modify"

print(list_2,copy_2)

Это дает нам вывод:

[['01', 'modify']] [['01', 'modify']]

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

Когда вы делаете:

copy_2=list_2[:]

Вы копируете только внешний список, а не внутренний. Мы можем использовать idфункцию еще раз, чтобы проверить это.

print(id(copy_2[0]))
print(id(list_2[0]))

Выход:

4329485832
4329485832

Когда мы это делаем copy_2=list_2[:], это происходит:

введите описание изображения здесь

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

Каково решение? Решением является deepcopyфункция.

from copy import deepcopy
deep=deepcopy(list_2)

Давайте проверим это:

print(id((list_2)),id(deep))

4322146056 4322148040

Оба внешних списка имеют разные идентификаторы, давайте попробуем это во внутренних вложенных списках.

print(id(deep[0]))
print(id(list_2[0]))

Выход:

4322145992
4322145800

Как видите, оба идентификатора различны, что означает, что мы можем предположить, что оба вложенных списка теперь указывают на разные объекты.

Это означает, что когда вы делаете deep=deepcopy(list_2)то, что на самом деле происходит:

введите описание изображения здесь

Оба вложенных списка указывают на разные объекты, и теперь у них есть отдельная копия вложенного списка.

Теперь давайте попробуем изменить вложенный список и посмотреть, решил ли он предыдущую проблему:

deep[0][1]="modify"
print(list_2,deep)

Это выводит:

[['01', '98']] [['01', 'modify']]

Как видите, он не изменил исходный вложенный список, он только изменил скопированный список.



34

Сроки Python 3.6

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

Я придерживался только создания мелких копий, а также добавил некоторые новые методы, которые были невозможны в Python2, такие как list.copy()(эквивалент фрагмента Python3 ) и две формы распаковки списка ( *new_list, = listи new_list = [*list]):

METHOD                  TIME TAKEN
b = [*a]                2.75180600000021
b = a * 1               3.50215399999990
b = a[:]                3.78278899999986  # Python2 winner (see above)
b = a.copy()            4.20556500000020  # Python3 "slice equivalent" (see above)
b = []; b.extend(a)     4.68069800000012
b = a[0:len(a)]         6.84498999999959
*b, = a                 7.54031799999984
b = list(a)             7.75815899999997
b = [i for i in a]      18.4886440000000
b = copy.copy(a)        18.8254879999999
b = []
for item in a:
  b.append(item)        35.4729199999997

Мы видим, что победитель Python2 по-прежнему преуспевает, но не сильно вытесняет Python3 list.copy(), особенно учитывая его превосходную читаемость.

Темная лошадка - это метод распаковки и повторной упаковки ( b = [*a]), который на ~ 25% быстрее, чем нарезка в сыром виде, и более чем в два раза быстрее, чем другой метод распаковки (*b, = a ).

b = a * 1 также на удивление хорошо.

Обратите внимание, что эти методы не выводят эквивалентные результаты для любого ввода, кроме списков. Все они работают для срезаемых объектов, некоторые работают для любых итеративных, но copy.copy()работают только для более общих объектов Python.


Вот код тестирования для заинтересованных сторон ( Шаблон отсюда ):

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", 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\t", timeit.timeit(stmt='b = a[0:len(a)]', setup=setup, number=COUNT))
print("*b, = a\t\t\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 = []; for 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))
print("b = [*a]\t\t", timeit.timeit(stmt='b = [*a]', setup=setup, number=COUNT))
print("b = a * 1\t\t", timeit.timeit(stmt='b = a * 1', setup=setup, number=COUNT))

1
Можно подтвердить еще похожую историю на 3.8 b=[*a]- единственный очевидный способ сделать это;).
SuperShoot

20

Все остальные участники дали отличные ответы, которые работают, когда у вас есть одноуровневый (выровненный) список, однако методы, упомянутые до сих пор, copy.deepcopy()работают только для клонирования / копирования списка и не позволяют указывать на вложенные listобъекты, когда вы работа с многомерными, вложенными списками (list of lists). Хотя Феликс Клинг ссылается на это в своем ответе, есть еще немного проблемы, и, возможно, обходной путь, использующий встроенные модули, которые могут оказаться более быстрой альтернативой deepcopy.

В то время new_list = old_list[:], copy.copy(old_list)'и для Py3k old_list.copy()работы одноуровневых списков, они возвращаются к указывая на listобъектах , вложенных в пределах old_listи new_list, а также изменениях в одном изlist объектов увековечены в других.

Редактировать: новая информация, представленная на свет

Как было отмечено Аароном Холлом и PM 2Ring, использование eval()не только плохая идея, но и намного медленнее, чемcopy.deepcopy() .

Это означает, что для многомерных списков единственным вариантом является copy.deepcopy() . С учетом вышесказанного, это действительно не вариант, поскольку производительность снижается, когда вы пытаетесь использовать ее в многомерном массиве среднего размера. Я попытался timeitиспользовать массив размером 42x42, что было неслыханно или даже слишком много для приложений биоинформатики, и я разочаровался в ожидании ответа и просто начал печатать мое редактирование этого поста.

Казалось бы, единственный реальный вариант - инициализировать несколько списков и работать с ними независимо. Если у кого-то есть какие-либо другие предложения относительно того, как обрабатывать многомерное копирование списка, это будет оценено.

Как уже говорили другие, существуют значительные проблемы с производительностью при использовании copyмодуля и copy.deepcopy для многомерных списков .


5
Это не всегда работает, поскольку нет гарантии, что возвращаемой строки repr()достаточно для воссоздания объекта. Кроме того, eval()это инструмент последней инстанции; см. Эвал действительно опасен для SO таким ветераном Недом Батчелдером. Поэтому, когда вы пропагандируете использование, eval()вам действительно следует упомянуть, что оно может быть опасным.
PM 2Ring 10.07.15

1
Честная оценка. Хотя я думаю, что смысл Батчелдера в том, что наличие eval()функции в Python в целом является риском. Дело не в том, используете ли вы функцию в коде, а в том, что это дыра в безопасности Python сама по себе. Мой пример не использует его с функцией , которая получает входные данные из input(), sys.agrvили даже текстового файла. Это больше похоже на инициализацию пустого многомерного списка один раз, а затем просто способ его копирования в цикле вместо повторной инициализации на каждой итерации цикла.
AMR

1
Как отметил @AaronHall, вероятно, существует значительная проблема с производительностью new_list = eval(repr(old_list)), поэтому, помимо плохой идеи, она, вероятно, слишком медленная для работы.
AMR

13

Меня удивляет, что это еще не было упомянуто, поэтому ради полноты ...

Вы можете выполнить распаковку списка с помощью "оператора сплат":, *который также будет копировать элементы вашего списка.

old_list = [1, 2, 3]

new_list = [*old_list]

new_list.append(4)
old_list == [1, 2, 3]
new_list == [1, 2, 3, 4]

Очевидным недостатком этого метода является то, что он доступен только в Python 3.5+.

Тем не менее, это разумно, чем другие распространенные методы.

x = [random.random() for _ in range(1000)]

%timeit a = list(x)
%timeit a = x.copy()
%timeit a = x[:]

%timeit a = [*x]

#: 2.47 µs ± 38.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
#: 2.47 µs ± 54.6 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
#: 2.39 µs ± 58.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

#: 2.22 µs ± 43.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

1
Как ведет себя этот метод при изменении копий?
not2qubit

2
@ not2qubit вы имеете в виду добавление или редактирование элементов нового списка. В этом примере old_listи new_listпредставлены два разных списка, редактирование одного не изменит другого (если только вы не изменили сами элементы (например, список списков), ни один из этих методов не является глубоким копированием).
SCB

8

Очень простой подход, независимый от версии Python, отсутствовал в уже предоставленных ответах, которые вы можете использовать большую часть времени (по крайней мере, я делаю):

new_list = my_list * 1       #Solution 1 when you are not using nested lists

Однако, если my_list содержит другие контейнеры (например, для вложенных списков), вы должны использовать deepcopy, как другие предложили в ответах выше из библиотеки копирования. Например:

import copy
new_list = copy.deepcopy(my_list)   #Solution 2 when you are using nested lists

, Бонус : если вы не хотите копировать элементы, используйте (или мелкое копирование):

new_list = my_list[:]

Давайте поймем разницу между решением № 1 и решением № 2

>>> a = range(5)
>>> b = a*1
>>> a,b
([0, 1, 2, 3, 4], [0, 1, 2, 3, 4])
>>> a[2] = 55 
>>> a,b
([0, 1, 55, 3, 4], [0, 1, 2, 3, 4])

Как видите, решение № 1 работало идеально, когда мы не использовали вложенные списки. Давайте проверим, что произойдет, когда мы применим решение № 1 к вложенным спискам.

>>> from copy import deepcopy
>>> a = [range(i,i+4) for i in range(3)]
>>> a
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]]
>>> b = a*1
>>> c = deepcopy(a)
>>> for i in (a, b, c): print i   
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]]
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]]
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]]
>>> a[2].append('99')
>>> for i in (a, b, c): print i   
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5, 99]]
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5, 99]]   #Solution#1 didn't work in nested list
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]]       #Solution #2 - DeepCopy worked in nested list

8

Обратите внимание, что в некоторых случаях, если вы определили свой собственный класс и хотите сохранить атрибуты, вам следует использовать copy.copy()или copy.deepcopy()вместо них альтернативы, например, в Python 3:

import copy

class MyList(list):
    pass

lst = MyList([1,2,3])

lst.name = 'custom list'

d = {
'original': lst,
'slicecopy' : lst[:],
'lstcopy' : lst.copy(),
'copycopy': copy.copy(lst),
'deepcopy': copy.deepcopy(lst)
}


for k,v in d.items():
    print('lst: {}'.format(k), end=', ')
    try:
        name = v.name
    except AttributeError:
        name = 'NA'
    print('name: {}'.format(name))

Выходы:

lst: original, name: custom list
lst: slicecopy, name: NA
lst: lstcopy, name: NA
lst: copycopy, name: custom list
lst: deepcopy, name: custom list

5
new_list = my_list[:]

new_list = my_list Попытайся понять это. Предположим, что my_list находится в куче памяти в местоположении X, т.е. my_list указывает на X. Теперь, присваивая new_list = my_listвам значение New_list, указывающее на X. Это называется мелким копированием.

Теперь, если вы назначаете, new_list = my_list[:]вы просто копируете каждый объект my_list в new_list. Это известно как Глубокая копия.

Другой способ сделать это:

  • new_list = list(old_list)
  • import copy new_list = copy.deepcopy(old_list)

3

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

В основе любой функции глубокого копирования лежит способ создания мелкой копии. Как? Просто. Любая функция глубокого копирования только дублирует контейнеры неизменяемых объектов. Когда вы глубоко копируете вложенный список, вы дублируете только внешние списки, а не изменяемые объекты внутри списков. Вы только дублируете контейнеры. То же самое работает и для классов. Когда вы копируете класс глубоко, вы копируете все его изменяемые атрибуты. Так как? Почему у вас есть только копировать контейнеры, такие как списки, dicts, кортежи, iters, классы и экземпляры классов?

Это просто. Изменчивый объект не может быть дублирован. Его нельзя изменить, поэтому это всего лишь одно значение. Это означает, что вам никогда не придется дублировать строки, числа, bools или любые другие. Но как бы вы продублировали контейнеры? Просто. Вы просто инициализируете новый контейнер со всеми значениями. Deepcopy опирается на рекурсию. Он дублирует все контейнеры, даже те, в которых есть контейнеры, до тех пор, пока не останется ни одного контейнера. Контейнер является неизменным объектом.

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

def deepcopy(x):
  immutables = (str, int, bool, float)
  mutables = (list, dict, tuple)
  if isinstance(x, immutables):
    return x
  elif isinstance(x, mutables):
    if isinstance(x, tuple):
      return tuple(deepcopy(list(x)))
    elif isinstance(x, list):
      return [deepcopy(y) for y in x]
    elif isinstance(x, dict):
      values = [deepcopy(y) for y in list(x.values())]
      keys = list(x.keys())
      return dict(zip(keys, values))

Собственная встроенная глубокая копия Python основана на этом примере. Единственное отличие состоит в том, что он поддерживает другие типы, а также поддерживает пользовательские классы, дублируя атрибуты в новый дублирующий класс, а также блокирует бесконечную рекурсию со ссылкой на объект, который уже просматривается с использованием списка заметок или словаря. И это действительно для создания глубоких копий. По своей сути создание глубокой копии - это просто мелкая копия. Я надеюсь, что этот ответ добавляет что-то к вопросу.

ПРИМЕРЫ

Скажем, у вас есть этот список: [1, 2, 3] . Неизменяемые числа не могут быть дублированы, но другой слой может. Вы можете продублировать его, используя понимание списка: [x для x в [1, 2, 3]

Теперь представьте, что у вас есть этот список: [[1, 2], [3, 4], [5, 6]] . На этот раз вы хотите создать функцию, которая использует рекурсию для глубокого копирования всех слоев списка. Вместо предыдущего понимания списка:

[x for x in _list]

Он использует новый для списков:

[deepcopy_list(x) for x in _list]

И deepcopy_list выглядит так:

def deepcopy_list(x):
  if isinstance(x, (str, bool, float, int)):
    return x
  else:
    return [deepcopy_list(y) for y in x]

Теперь у вас есть функция, которая может копировать любой список strs, bools, floast, ints и даже списки в бесконечное число слоев, используя рекурсию. И вот, у вас это есть, глубокий копирование.

TLDR : Deepcopy использует рекурсию для дублирования объектов и просто возвращает те же неизменяемые объекты, что и раньше, поскольку неизменяемые объекты не могут дублироваться. Однако он копирует самые внутренние слои изменяемых объектов до тех пор, пока не достигнет самого изменяемого слоя объекта.


3

Небольшая практическая перспектива заглянуть в память через id и gc.

>>> b = a = ['hell', 'word']
>>> c = ['hell', 'word']

>>> id(a), id(b), id(c)
(4424020872, 4424020872, 4423979272) 
     |           |
      -----------

>>> id(a[0]), id(b[0]), id(c[0])
(4424018328, 4424018328, 4424018328) # all referring to same 'hell'
     |           |           |
      -----------------------

>>> id(a[0][0]), id(b[0][0]), id(c[0][0])
(4422785208, 4422785208, 4422785208) # all referring to same 'h'
     |           |           |
      -----------------------

>>> a[0] += 'o'
>>> a,b,c
(['hello', 'word'], ['hello', 'word'], ['hell', 'word'])  # b changed too
>>> id(a[0]), id(b[0]), id(c[0])
(4424018384, 4424018384, 4424018328) # augmented assignment changed a[0],b[0]
     |           |
      -----------

>>> b = a = ['hell', 'word']
>>> id(a[0]), id(b[0]), id(c[0])
(4424018328, 4424018328, 4424018328) # the same hell
     |           |           |
      -----------------------

>>> import gc
>>> gc.get_referrers(a[0]) 
[['hell', 'word'], ['hell', 'word']]  # one copy belong to a,b, the another for c
>>> gc.get_referrers(('hell'))
[['hell', 'word'], ['hell', 'word'], ('hell', None)] # ('hello', None) 

3

Помните, что в Python, когда вы делаете:

    list1 = ['apples','bananas','pineapples']
    list2 = list1

List2 хранит не фактический список, а ссылку на list1. Поэтому, когда вы делаете что-либо для list1, list2 также меняется. используйте модуль копирования (не по умолчанию, скачать на pip), чтобы сделать оригинальную копию списка ( copy.copy()для простых списков, copy.deepcopy()для вложенных). Это делает копию, которая не изменяется с первым списком.


1

Опция Deepcopy - единственный метод, который работает для меня:

from copy import deepcopy

a = [   [ list(range(1, 3)) for i in range(3) ]   ]
b = deepcopy(a)
b[0][1]=[3]
print('Deep:')
print(a)
print(b)
print('-----------------------------')
a = [   [ list(range(1, 3)) for i in range(3) ]   ]
b = a*1
b[0][1]=[3]
print('*1:')
print(a)
print(b)
print('-----------------------------')
a = [   [ list(range(1, 3)) for i in range(3) ] ]
b = a[:]
b[0][1]=[3]
print('Vector copy:')
print(a)
print(b)
print('-----------------------------')
a = [   [ list(range(1, 3)) for i in range(3) ]  ]
b = list(a)
b[0][1]=[3]
print('List copy:')
print(a)
print(b)
print('-----------------------------')
a = [   [ list(range(1, 3)) for i in range(3) ]  ]
b = a.copy()
b[0][1]=[3]
print('.copy():')
print(a)
print(b)
print('-----------------------------')
a = [   [ list(range(1, 3)) for i in range(3) ]  ]
b = a
b[0][1]=[3]
print('Shallow:')
print(a)
print(b)
print('-----------------------------')

приводит к выводу:

Deep:
[[[1, 2], [1, 2], [1, 2]]]
[[[1, 2], [3], [1, 2]]]
-----------------------------
*1:
[[[1, 2], [3], [1, 2]]]
[[[1, 2], [3], [1, 2]]]
-----------------------------
Vector copy:
[[[1, 2], [3], [1, 2]]]
[[[1, 2], [3], [1, 2]]]
-----------------------------
List copy:
[[[1, 2], [3], [1, 2]]]
[[[1, 2], [3], [1, 2]]]
-----------------------------
.copy():
[[[1, 2], [3], [1, 2]]]
[[[1, 2], [3], [1, 2]]]
-----------------------------
Shallow:
[[[1, 2], [3], [1, 2]]]
[[[1, 2], [3], [1, 2]]]
-----------------------------

1

Это потому, что строка new_list = my_listназначает новую ссылку на переменную, my_listкоторая new_list похожа на Cкод, приведенный ниже,

int my_list[] = [1,2,3,4];
int *new_list;
new_list = my_list;

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

import copy
new_list = copy.deepcopy(my_list)
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.