Как клонировать список, чтобы он не изменялся неожиданно после присваивания?
При использовании new_list = my_list
любые изменения, внесенные в new_list
, также изменяют my_list
каждый раз. Почему это происходит и как я могу клонировать или скопировать список, чтобы этого избежать? Например:
>>> my_list = [1, 2, 3]
>>> new_list = my_list
>>> new_list.append(4)
>>> my_list
[1, 2, 3, 4]
3 ответ(ов)
Идиомой Python для создания копии списка является newList = oldList[:]
. Этот синтаксис создает поверхностную копию списка oldList
, что позволяет избежать воздействия изменений на оригинальный список, когда вы работаете с newList
.
Когда вы пишете new_list = my_list
, вы просто создаёте новое имя для той же самой ссылки на объект в памяти, который хранится по адресу X. Это означает, что new_list
и my_list
указывают на один и тот же объект в куче (heap memory). Это называется поверхностным копированием (shallow copy).
С другой стороны, когда вы используете new_list = my_list[:]
, вы создаёте новый список, который содержит копии каждого элемента из my_list
. Это называется глубоким копированием (deep copy), хотя на самом деле это всё же поверхностное копирование объектов, если сами объекты изменяемы.
Другие способы сделать это:
Используя встроенную функцию
list
:new_list = list(old_list)
С использованием модуля
copy
для глубокого копирования:import copy new_list = copy.deepcopy(old_list)
Таким образом, если вам нужно создать независимую копию списка, лучше всего использовать my_list[:]
или list(my_list)
. Если же вам нужно глубокое копирование со всеми вложенными изменяемыми объектами, используйте copy.deepcopy()
.
Ответ на ваш вопрос можно изложить следующим образом:
В среди уже предложенных решений отсутствует очень простой подход, который будет работать независимо от версии Python и который я использую чаще всего (по крайней мере, я так делаю):
new_list = my_list * 1 # Решение 1, если вы не используете вложенные списки
Однако, если my_list содержит другие контейнеры (например, вложенные списки), вам придется использовать deepcopy, как уже было предложено в ответах выше из библиотеки copy. Например:
import copy
new_list = copy.deepcopy(my_list) # Решение 2, если вы используете вложенные списки
Бонус: Если вы не хотите копировать элементы, используйте (так называемое) поверхностное копирование:
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]] # Решение #1 не сработало для вложенного списка
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]] # Решение #2 - DeepCopy сработало для вложенного списка
Таким образом, решение #1 работает только для плоских списков, в то время как решение #2 с использованием deepcopy подходит для вложенных списков.
Почему используется string.join(list), а не list.join(string)?
Как получить последний элемент списка?
Как вернуть ключи словаря в виде списка в Python?
Как отсортировать список/кортеж списков/кортежей по элементу на заданном индексе
Как преобразовать строковое представление списка в список?