How do I clone a list so that it doesn't change unexpectedly after assignment?
Как мне клонировать список, чтобы он неожиданно не изменился после назначения?
При использовании new_list = my_list любые модификации new_list изменяются my_list каждый раз. Почему это так, и как я могу клонировать или копировать список, чтобы предотвратить это?
Переведено автоматически
Ответ 1
new_list = my_list на самом деле второй список не создается. Назначение просто копирует ссылку на список, а не сам список, поэтому оба new_list и my_list ссылаются на один и тот же список после назначения.
Чтобы фактически скопировать список, у вас есть несколько вариантов:
Вы можете использовать встроенный list.copy() метод (доступен начиная с Python 3.3):
new_list = old_list.copy()
Вы можете нарезать его:
new_list = old_list[:]
Мнение Алекса Мартелли (по крайней мере, еще в 2007 году) по этому поводу таково, что это странный синтаксис и нет смысла его когда-либо использовать. ;) (По его мнению, следующий вариант более удобочитаем).
Вы можете использовать встроенный list() конструктор:
Это немного медленнее, чем list() потому что сначала нужно определить тип данных old_list.
Если вам также нужно скопировать элементы списка, используйте generic copy.deepcopy():
import copy new_list = copy.deepcopy(old_list)
Очевидно, что это самый медленный метод, требующий наибольшего объема памяти, но иногда неизбежный. Он работает рекурсивно; он будет обрабатывать любое количество уровней вложенных списков (или других контейнеров).
Пример:
import copy
classFoo(object): def__init__(self, val): self.val = val
def__repr__(self): returnf'Foo({self.val!r})'
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
Итак, самое быстрое - это нарезка списка. Но имейте в виду, что copy.copy(), list[:] и list(list), в отличие от copy.deepcopy() и версии python, не копируют никаких списков, словарей и экземпляров классов в списке, поэтому, если оригиналы изменятся, они изменятся и в скопированном списке, и наоборот.
(Вот сценарий, если кому-то интересно или он хочет поднять какие-либо вопросы:)
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)): iftype(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: iftype(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
Какие есть варианты клонирования или копирования списка в 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[:]
Вы также можете выполнить то же самое, передав список через конструктор list,
Использование new_list = my_list затем изменяет new_list каждый раз, когда меняется my_list . Почему это?
my_list это просто имя, которое указывает на фактический список в памяти. Когда вы говорите, new_list = my_list вы не создаете копию, вы просто добавляете другое имя, которое указывает на этот исходный список в памяти. У нас могут возникнуть аналогичные проблемы при создании копий списков.
Список - это просто массив указателей на содержимое, поэтому поверхностная копия просто копирует указатели, и таким образом, у вас есть два разных списка, но они имеют одинаковое содержимое. Чтобы сделать копии содержимого, вам нужна глубокая копия.
Итак, мы видим, что список с глубоким копированием полностью отличается от исходного. Вы могли бы создать свою собственную функцию, но не делайте этого. Вы, вероятно, создадите ошибки, которых в противном случае не было бы, используя функцию deepcopy стандартной библиотеки.
Не используйте eval
Вы можете увидеть, что это используется как способ глубокого копирования, но не делайте этого:
problematic_deep_copy = eval(repr(a_list))
Это опасно, особенно если вы оцениваете что-то из источника, которому вы не доверяете.
Ненадежно, если копируемый вами подэлемент не имеет представления, которое можно оценить для воспроизведения эквивалентного элемента.