33

Как клонировать список, чтобы он не изменялся неожиданно после присваивания?

15

При использовании 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 ответ(ов)

0

Идиомой Python для создания копии списка является newList = oldList[:]. Этот синтаксис создает поверхностную копию списка oldList, что позволяет избежать воздействия изменений на оригинальный список, когда вы работаете с newList.

0

Когда вы пишете 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().

0

Ответ на ваш вопрос можно изложить следующим образом:

В среди уже предложенных решений отсутствует очень простой подход, который будет работать независимо от версии 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 подходит для вложенных списков.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь