Список изменений неожиданно после назначения. Почему это так и как это предотвратить?

2905

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

0
3678

С у 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сначала нужно узнать тип данных .

  • Если список содержит объекты, и вы также хотите их скопировать, используйте общий 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)]
5
  • Как правильно указывает @Georgy в ответе ниже, любые изменения значений new_list также изменят значения в my_list. Таким образом, на самом деле метод copy.deepcopy () является единственной реальной копией без ссылки на исходный список и его значения. moojen 21 дек.
  • @ Эрри, я думаю, ты ошибся. Я не публиковал здесь ни ответов, ни комментариев :)Georgy 21 дек.
  • Вы правы, он был отредактирован вами, но опубликован @cryo Извините за путаницу! moojen 21 дек.
  • Какой из них самый быстрый? zzz777 4 мая в 18:10
  • У меня была такая же проблема со списком json (каждый элемент списка был json), и единственный, который работал, был new_list = copy.deepcopy (old_list); Я пишу это, потому что любой может столкнуться с такой же проблемой. Спасибо! Tom 11 мая в 15:23
685

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

  1. 10,59 с (105,9 мкс / itn) - copy.deepcopy(old_list)
  2. 10,16 с (101,6 мкс / итн) - чистый Copy()метод Python, копирующий классы с глубокой копией
  3. 1,488 с (14,88 мкс / итн) - чистый Copy()метод Python, не копирующий классы (только dicts / списки / кортежи)
  4. 0,325 с (3,25 мкс / itn) - 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 мкс / itn) - list(old_list)
  8. 0,053 с (0,53 мкс / itn) - new_list = []; new_list.extend(old_list)
  9. 0,039 сек (0,39 мкс / 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
2
  • Означает ли это, что добавление и понимание списка - лучшие варианты? zzz777 4 мая в 18:24
  • У меня есть кеш, содержащий список классов, я хочу снять блокировку, скопировать список, снять блокировку. Надеюсь, что достаточно использовать встроенную копию, чтобы защитить скопированный список от изменения при изменении кешированной копии. zzz777 4 мая в 18:32
173

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

newlist = old_list.copy()
4
  • 12
    Да, и в соответствии с DOCS docs.python.org/3/library/stdtypes.html#mutable-sequence-types , s.copy()создает неполную копию s(такой же , как s[:]). CyberMew 25 сен.
  • 4
    На самом деле, кажется , что в настоящее время python3.8, .copy()это немного быстрее , чем нарезка. См. Ниже ответ @AaronsHall. loved.by.Jesus 24 апр '20 в 8:11
  • @ Love.by.Jesus: Да, они добавили оптимизацию для вызовов методов уровня Python в 3.7, которые были расширены до вызовов методов расширения C в 3.8 с помощью PEP 590, которые устраняют накладные расходы на создание связанного метода каждый раз, когда вы вызываете метод, поэтому Стоимость вызова alist.copy()теперь представляет собой dictпоиск по listтипу, а затем относительно дешевый вызов функции без аргументов, который в конечном итоге вызывает то же, что и нарезка. Нарезка по-прежнему должна создать sliceобъект, а затем пройти проверку типа и распаковку, чтобы сделать то же самое. ShadowRanger 30 ноя '20 в 18:33
  • 3
    Конечно, они работают над оптимизацией повторяющихся построений постоянных срезов , поэтому в 3.10 срезы снова могут победить. Хотя все это довольно бессмысленно; асимптотическая производительность идентична, а фиксированные накладные расходы относительно невелики, поэтому на самом деле не имеет значения, какой подход вы используете. ShadowRanger 30 ноя '20 в 18:36
138

What are the options to clone or copy a list in 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

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

Using new_list = my_list then modifies new_list every time my_list changes. Why is this?

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
  • 2
    Вам не нужна глубокая копия, если список двухмерный. Если это список списков, и в этих списках нет списков внутри них, вы можете использовать цикл for. Сейчас использую, list_copy=[] for item in list: list_copy.append(copy(item))и это намного быстрее. John Locke 10 янв.
62

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

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)

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

1
  • Когда я занимался этой темой, мне следовало прокрутить вниз до вашего ответа. Мне было трудно понять, почему моя копия "списка списков" на самом деле не является копией ... :-) Спасибо за это! MrZH6 10 сен '20 в 15:37
55

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

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

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

Итак, он изменил исходный список:

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

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

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

0
40

Использовать thing[:]

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

Python 3.6 Тайминги

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

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

METHOD                TIME TAKEN
b = [*a]               2.75180600000021
b = a * 1              3.50215399999990
b = a[:]               3.78278899999986  # Python 2 winner (see above)
b = a.copy()           4.20556500000020  # Python 3 "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

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

Темная лошадка - это метод распаковки и переупаковки ( 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))
4
  • 1
    Могу подтвердить еще похожую историю на 3.8 b=[*a]- один очевидный способ сделать это Smile. SuperShoot 02 марта '20 в 0:02
  • 1
    Некоторые из этих сравнений по времени не имеют особого смысла при копировании таких крошечных списков. Было бы более информативным тестировать с диапазоном длин списков (включая некоторые очень большие). ekhumoro 21 ноя '20 в 20:15
  • Числа времени должны быть округлены до соответствующего количества значащих цифр. 15 значащих цифр не имеют никакого смысла. Peter Mortensen 11 мая в 21:43
  • По сути, я просто вставил сюда необработанный вывод временного кода. Похоже, ваша проблема связана с тем, как timeit отображает тайминги, которые я мало контролирую. River 18 мая в 0:38
35

Идиома Python для этого: newList = oldList[:]

22

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

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

Изменить: появилась новая информация

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.

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

3
  • 5
    Это не всегда сработает, поскольку нет гарантии, что возвращенная строка repr()достаточна для воссоздания объекта. Кроме того, eval()это инструмент последней инстанции; см. Eval действительно опасно от ветерана SO Неда Батчелдера. Поэтому, когда вы выступаете за такое использование, eval()вам действительно следует упомянуть, что это может быть опасно. PM 2Ring 10 июл.
  • 1
    Честная оценка. Хотя я думаю, что точка зрения Батчелдера заключается в том, что наличие eval()функции в Python в целом является риском. Дело не столько в том, используете ли вы функцию в коде, сколько в том, что это дыра в безопасности в Python сама по себе. Мой пример не использует его с функцией , которая получает входные данные из input(), sys.agrvили даже текстового файла. Это больше похоже на инициализацию пустого многомерного списка один раз, а затем просто возможность скопировать его в цикле вместо повторной инициализации на каждой итерации цикла. AMR 10 июл.
  • 1
    Как отметил @AaronHall, вероятно, существует значительная проблема с производительностью при использовании new_list = eval(repr(old_list)), поэтому, помимо того, что это плохая идея, она, вероятно, также слишком медленная, чтобы работать. AMR 10 июл.
16

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

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

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)
2
  • 1
    Как этот метод ведет себя при изменении копий? not2qubit 24 сен '18 в 13:35
  • 2
    @ not2qubit означает добавление или редактирование элементов нового списка. В примере old_listи new_listпредставлены два разных списка, редактирование одного не изменит другой (если вы напрямую не изменяете сами элементы (например, список списка), ни один из этих методов не является глубокой копией). SCB 25 сен.
11

В уже предоставленных ответах отсутствовал очень простой подход, независимый от версии 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
6
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)
5

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

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

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

5

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

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

Это просто. Изменяемый объект не может быть продублирован. Его нельзя изменить, поэтому это только одно значение. Это означает, что вам никогда не придется дублировать строки, числа, логические значения или любые из них. Но как бы вы продублировали контейнеры? Простой. Вы просто инициализируете новый контейнер со всеми значениями. 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 Deepcopy основан на этом примере. Единственное отличие состоит в том, что он поддерживает другие типы, а также поддерживает пользовательские классы, дублируя атрибуты в новый повторяющийся класс, а также блокирует бесконечную рекурсию со ссылкой на объект, который он уже видел, используя список заметок или словарь. И это действительно все для создания глубоких копий. По сути, создание глубокой копии - это просто создание неглубоких копий. Надеюсь, этот ответ кое-что добавит к вопросу.

ПРИМЕРЫ

Скажем , у вас есть этот список: [1, 2, 3]. Неизменяемые числа не могут быть продублированы, но другой слой может. Вы можете продублировать его, используя понимание списка:[x for x in [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, int и даже списки на бесконечное количество слоев с использованием рекурсии. И вот оно, глубокое копирование.

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

4

Небольшая практическая перспектива заглянуть в память через 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) 
2

Опция 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
  • Deepcopy следует использовать только тогда, когда это необходимо, и нужно знать, что он на самом деле делает. Jean-François Fabre 19 ноя '20 в 13:43
2

Это потому, что строка 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)
2

Используемый метод зависит от содержимого копируемого списка. Если список содержит вложенные, то dictsDeepcopy - единственный метод, который работает, в противном случае большинство методов, перечисленных в ответах (срез, цикл [для], копирование, расширение, объединение или распаковка), будут работать и выполняться в одно и то же время (за исключением loop и deepcopy, которые были наихудшими).

Сценарий

from random import randint
from time import time
import copy

item_count = 100000

def copy_type(l1: list, l2: list):
  if l1 == l2:
    return 'shallow'
  return 'deep'

def run_time(start, end):
  run = end - start
  return int(run * 1000000)

def list_combine(data):
  l1 = [data for i in range(item_count)]
  start = time()
  l2 = [] + l1
  end = time()
  if type(data) == dict:
    l2[0]['test'].append(1)
  elif type(data) == list:
    l2.append(1)
  return {'method': 'combine', 'copy_type': copy_type(l1, l2), 
          'time_µs': run_time(start, end)}

def list_extend(data):
  l1 = [data for i in range(item_count)]
  start = time()
  l2 = []
  l2.extend(l1)
  end = time()
  if type(data) == dict:
    l2[0]['test'].append(1)
  elif type(data) == list:
    l2.append(1)
  return {'method': 'extend', 'copy_type': copy_type(l1, l2), 
          'time_µs': run_time(start, end)}

def list_unpack(data):
  l1 = [data for i in range(item_count)]
  start = time()
  l2 = [*l1]
  end = time()
  if type(data) == dict:
    l2[0]['test'].append(1)
  elif type(data) == list:
    l2.append(1)
  return {'method': 'unpack', 'copy_type': copy_type(l1, l2), 
          'time_µs': run_time(start, end)}

def list_deepcopy(data):
  l1 = [data for i in range(item_count)]
  start = time()
  l2 = copy.deepcopy(l1)
  end = time()
  if type(data) == dict:
    l2[0]['test'].append(1)
  elif type(data) == list:
    l2.append(1)
  return {'method': 'deepcopy', 'copy_type': copy_type(l1, l2), 
          'time_µs': run_time(start, end)}

def list_copy(data):
  l1 = [data for i in range(item_count)]
  start = time()
  l2 = list.copy(l1)
  end = time()
  if type(data) == dict:
    l2[0]['test'].append(1)
  elif type(data) == list:
    l2.append(1)
  return {'method': 'copy', 'copy_type': copy_type(l1, l2), 
          'time_µs': run_time(start, end)}

def list_slice(data):
  l1 = [data for i in range(item_count)]
  start = time()
  l2 = l1[:]
  end = time()
  if type(data) == dict:
    l2[0]['test'].append(1)
  elif type(data) == list:
    l2.append(1)
  return {'method': 'slice', 'copy_type': copy_type(l1, l2), 
          'time_µs': run_time(start, end)}

def list_loop(data):
  l1 = [data for i in range(item_count)]
  start = time()
  l2 = []
  for i in range(len(l1)):
    l2.append(l1[i])
  end = time()
  if type(data) == dict:
    l2[0]['test'].append(1)
  elif type(data) == list:
    l2.append(1)
  return {'method': 'loop', 'copy_type': copy_type(l1, l2), 
          'time_µs': run_time(start, end)}

def list_list(data):
  l1 = [data for i in range(item_count)]
  start = time()
  l2 = list(l1)
  end = time()
  if type(data) == dict:
    l2[0]['test'].append(1)
  elif type(data) == list:
    l2.append(1)
  return {'method': 'list()', 'copy_type': copy_type(l1, l2), 
          'time_µs': run_time(start, end)}

if __name__ == '__main__':
  list_type = [{'list[dict]': {'test': [1, 1]}}, 
          {'list[list]': [1, 1]}]
  store = []
  for data in list_type:
    key = list(data.keys())[0]
    store.append({key: [list_unpack(data[key]), list_extend(data[key]), 
                list_combine(data[key]), list_deepcopy(data[key]), 
                list_copy(data[key]), list_slice(data[key]),           
                list_loop(data[key])]})
  print(store)

Полученные результаты

[{"list[dict]": [
  {"method": "unpack", "copy_type": "shallow", "time_µs": 56149},
  {"method": "extend", "copy_type": "shallow", "time_µs": 52991},
  {"method": "combine", "copy_type": "shallow", "time_µs": 53726},
  {"method": "deepcopy", "copy_type": "deep", "time_µs": 2702616},
  {"method": "copy", "copy_type": "shallow", "time_µs": 52204},
  {"method": "slice", "copy_type": "shallow", "time_µs": 52223},
  {"method": "loop", "copy_type": "shallow", "time_µs": 836928}]},
{"list[list]": [
  {"method": "unpack", "copy_type": "deep", "time_µs": 52313},
  {"method": "extend", "copy_type": "deep", "time_µs": 52550},
  {"method": "combine", "copy_type": "deep", "time_µs": 53203},
  {"method": "deepcopy", "copy_type": "deep", "time_µs": 2608560},
  {"method": "copy", "copy_type": "deep", "time_µs": 53210},
  {"method": "slice", "copy_type": "deep", "time_µs": 52937},
  {"method": "loop", "copy_type": "deep", "time_µs": 834774}
]}]
0

Есть простой способ справиться с этим.

Код:

number=[1,2,3,4,5,6] #Original list
another=[] #another empty list
for a in number: #here I am declaring variable (a) as an item in the list (number)
    another.append(a) #here we are adding the items of list (number) to list (another)
print(another)

Выход:

>>> [1,2,3,4,5,6]

Надеюсь, это было полезно для вашего запроса.

0

Существует еще один способ копирования списка , который не был в список до сих пор: добавление пустого списка: l2 = l + [].

Я тестировал его на Python 3.8:

l = [1,2,3]
l2 = l + []
print(l,l2)
l[0] = 'a'
print(l,l2)

Это не лучший ответ, но он работает.

0