Перейти к содержанию

Копирование объектов

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

Поверхностная копия

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

Поверхностную копию можно сделать при помощи copy.copy().

Например:

import copy

class Test:
  pass

first = [Test()]
print("# first")
print(f"Container ID={id(first)}, el={id(first[0])}") 
# Container ID=140323889213760, el=140323888984464

copied = copy.copy(first)
print("# copied")
print(f"Container ID={id(copied)}, el={id(copied[0])}") 
# Container ID=140323889217152, el=140323888984464

Как видим, идентификаторы контейнеров у нас разные - при копировании создался новый список. А вот объект внутри списка новый не создался - ссылка в copied перешла ссылка на него из first.

Переопределить поведение функции можно при помощи магического метода __copy__.

Копирование иммутабельных объектов

Если мы попробуем скопировать иммутабельный объект, то получим ссылку на него. Пример с кортежом:

first = (1, 2, 3)
copied = copy.copy(first)
first is copied # True

Глубокая копия

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

first = [Test()]
print("# first")
print(f"Container ID={id(first)}, el={id(first[0])}") 
# Container ID=139994093187456, el=139994092958096

copied = copy.deepcopy(first)
print("# copied")
print(f"Container ID={id(copied)}, el={id(copied[0])}") 
# Container ID=139994093190784, el=139994093209920

Индентификатор контейнера и идентификатор объекта в контейнере разные.

Проблемы при создании глубокой копии

  • Рекурсивные объекты (которые содержат ссылки сами на себя) могут стать причиной рекурсивного цикла.
  • Не всё можно скопировать.
  • Иногда могут копироваться данные, которые должны быть разделяемы между копиями

deepcopy решает эти проблемы следующим способом:

  • Поведение при применении функции deepcopy можно поменять при помощи магического метода __deepcopy__. Таким образом при копировании можно не копировать некоторые объекты, а оставлять ссылку на них.
  • deepcopy может вторым аргументом принимать словарь memo, который хранит уже скопированные объекты во время текущего прохода копирования. Это помогает при копировании рекурсивных объектов.