copy () против deepcopy () в Python

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

Неизменяемые и изменяемые объекты

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

Например, предположим, что у нас есть две переменные, ссылающиеся на один и тот же целочисленный объект:

>>> a = 10
>>> b = a  # variables a and b hold the reference to the same object

Теперь, если мы выполним какую-либо операцию с переменной a— и при условии, что целые числа в Python неизменяемы, результатом будет, по сути, новый объект, содержащий новое значение. Это означает, что старое значение объекта (и, следовательно, все относящиеся к нему переменные) останется неизменным:

>>> a = a + 1
>>> print(a)
11
>>> print(b)
10

С другой стороны, изменяемые типы объектов позволяют изменять значение объекта на месте. Это означает, что изменение значения изменяемого типа объекта влияет на все переменные, содержащие ссылку на один и тот же объект. Например, предположим, что у нас есть следующие списки

>>> list_1 = [1, 2, 3]
>>> list_2 = list_1

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

>>> list_1[0] = 0
>>> print(list_1)
[0, 2, 3]
>>> print(list_2)
[0, 2, 3]

Для получения более подробной информации о модели динамического ввода Python вы можете прочитать одну из моих статей на Medium:



Нормальное назначение

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

a = [1, 2, 3]
b = a

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

>>> a[0] = 0
>>> print(a)
[0, 2, 3]
>>> print(b)
[0, 2, 3]

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

>>> id(a) == id(b)
True

Операторы присваивания в Python не копируют объекты, они создают привязки между целью и объектом - Python Docs

Неглубокая копия против глубокой копии

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

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

>>> import copy
>>> b = copy.copy(a)
>>> id(a) == id(b)
False

Как мы видим, объекты списков a и b отличаются, что означает, что они содержат разные ссылки, указывающие на разные объекты в памяти (даже если значения этих объектов одинаковы).

Когда нам нужно иметь дело с составными объектами, все становится немного сложнее. Теперь предположим, что переменная a - составной объект, представляющий список списков:

a = [[1, 2, 3], [4, 5, 6]]

Теперь возьмем неглубокую копию a

>>> import copy
>>> b = copy.copy(a)

и мы видим, что a и b - разные объекты

>>> id(a) == id(b)
False

однако внутренние объекты (т.е. два внутренних списка) такие же, как и те, на которые ссылается исходный объект:

>>> id(a[0]) == id(b[0])
True

Это довольно опасно, поскольку изменение любого из внутренних списков повлияет на другой составной объект, ссылающийся на эти внутренние списки:

>>> a[0][0] = 0
>>> a
[[0, 2, 3], [4, 5, 6]]
>>> b
[[0, 2, 3], [4, 5, 6]]

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

неглубокая копия создает новый составной объект, а затем (насколько это возможно) вставляет в него ссылки на объекты, найденные в оригинале - Документы Python

Глубокая копия будет копировать исходный объект, а затем рекурсивно копировать найденные внутренние объекты (если таковые имеются).

>>> import copy
>>> a = [[1, 2, 3], [4, 5, 6]]
>>> b = copy.deepcopy(a)

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

>>> id(a) == id(b)
False

но в этом случае даже внутренние объекты будут другими:

>>> id(a[0]) == id(b[0])
False

Это означает, что изменение любого из вложенных списков в a не повлияет на соответствующие списки в объекте b:

>>> a[0][0] = 0
>>> a
[[0, 2, 3], [4, 5, 6]]
>>> b
[[1, 2, 3], [4, 5, 6]]

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

глубокая копия создает новый составной объект, а затем рекурсивно вставляет в него копии объектов, найденных в оригинале - Python Docs

Заключение

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

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