Модуль copy

Операция присваивания не копирует объект, он лишь создаёт ссылку на объект. Для изменяемых коллекций, или для коллекций, содержащих изменяемые элементы, часто необходима такая копия, чтобы её можно было изменить, не изменяя оригинал. Данный модуль предоставляет общие (поверхностная и глубокая) операции копирования.

copy.copy(x) – возвращает поверхностную копию x.

copy.deepcopy(x) – возвращает полную копию x.

Исключениеcopy.error – возникает, если объект невозможно скопировать.

Разница между поверхностным и глубоким копированием существенна только для составных объектов, содержащих изменяемые объекты (например, список списков, или словарь, в качестве значений которого – списки или словари):

  • Поверхностная копия создает новый составной объект, и затем (по мере возможности) вставляет в него ссылки на объекты, находящиеся в оригинале.
  • Глубокая копия создает новый составной объект, и затем рекурсивно вставляет в него копии объектов, находящихся в оригинале.
>>> import copy
>>> test_1 = [1, 2, 3, [1, 2, 3]]
>>> test_copy = copy.copy(test_1)
>>> print(test_1, test_copy)
[1, 2, 3, [1, 2, 3]] [1, 2, 3, [1, 2, 3]]
>>> test_copy[3].append(4)
>>> print(test_1, test_copy)
[1, 2, 3, [1, 2, 3, 4]] [1, 2, 3, [1, 2, 3, 4]]
>>> test_1 = [1, 2, 3, [1, 2, 3]]
>>> test_deepcopy = copy.deepcopy(test_1)
>>> test_deepcopy[3].append(4)
>>> print(test_1, test_deepcopy)
[1, 2, 3, [1, 2, 3]] [1, 2, 3, [1, 2, 3, 4]]

Для операции глубокого копирования часто возникают две проблемы, которых нет у операции поверхностного копирования:

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

Функция deepcopy решает эти проблемы путем:

  • Хранения “memo” словаря объектов, скопированных во время текущего прохода копирования;
  • Позволения классам, определенным пользователем, переопределять операцию копирования или набор копируемых компонентов.
>>> r = [1, 2, 3]
>>> r.append(r)
>>> print(r)
[1, 2, 3, [...]]
>>> p = copy.deepcopy(r)
>>> print(p)
[1, 2, 3, [...]]

Этот модуль не копирует типы вроде модулейклассовфункций, методов, следа в стеке, стековых кадров, файлов, сокетов, окон, и подобных типов.

Поверхностная копия изменяемых объектов также может быть создана методом .copy() у списков (начиная с Python 3.3), присваиванием среза (copied_list = original_list[:]), методом .copy() словарей и множеств.

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

Для того, чтобы определить собственную реализацию копирования, класс может определить специальные методы __copy__() и __deepcopy__().

Первый вызывается для реализации операции поверхностного копирования; дополнительных аргументов не передается.

Второй вызывается для реализации операции глубокого копирования; ему передается один аргумент, словарь memo. Если реализация __deepcopy__() нуждается в создании глубокой копии компонента, то он должен вызвать функцию deepcopy() с компонентом в качестве первого аргумента и словарем memo в качестве второго аргумента.

Как использовать функцию copy в Python?

Мелкая копия — это функция, используемая для создания копии существующего объекта коллекции. Когда мы пытаемся скопировать объект коллекции с помощью неглубокой копии, эта функция создает новый объект коллекции и сохраняет ссылки на элементы исходного объекта.

Мы можем использовать неглубокое копирование в Python с помощью модуля копирования. Для выполнения операции неглубокого копирования мы используем метод copy модуля copy.

Метод copy принимает исходный объект коллекции в качестве входных данных и создает новый объект коллекции со ссылкой на элементы исходного объекта коллекции. Затем он возвращает ссылку на новый объект коллекции.

В следующем примере я создам копию данного словаря Python с помощью метода copy:

import copy

data = {1: 5, 2: 6, 3: 7, 4: 8}
print("Оригинальный словарь:")
print(data)
new_data = copy.copy(data)
print("Новый словарь:")
print(new_data)

Вывод программы:

Оригинальный словарь:
{1: 5, 2: 6, 3: 7, 4: 8}
Новый словарь:
{1: 5, 2: 6, 3: 7, 4: 8}

В выходных данных мы видим, что мы создали такой же словарь, как и исходный словарь, приведенный выше.

Как работает неглубокое копирование?


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

Рассмотрим пример:

import copy

data = {1: 5, 2: 6, 3: 7, 4: 8}
print("Оригинальный словарь:")
print(data)
new_data = copy.copy(data)
print("Новый словарь:")
print(new_data)
new_data[1] = 99
print("Оригинальный словарь после изменения:")
print(data)
print("Новый словарь после изменения:")
print(new_data)

Вывод программы:

Оригинальный словарь:
{1: 5, 2: 6, 3: 7, 4: 8}
Новый словарь:
{1: 5, 2: 6, 3: 7, 4: 8}
Оригинальный словарь после изменения:
{1: 5, 2: 6, 3: 7, 4: 8}
Новый словарь после изменения:
{1: 99, 2: 6, 3: 7, 4: 8}

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

Например:

import copy

data = {1: 5, 2: 6, 3: 7, 4: 8}
print("Оригинальный словарь:")
print(data)
new_data = copy.copy(data)
print("Новый словарь:")
print(new_data)
data[1] = 99
print("Оригинальный словарь после изменения:")
print(data)
print("Новый словарь после изменения:")
print(new_data)

Вывод программы:

Оригинальный словарь:
{1: 5, 2: 6, 3: 7, 4: 8}
Новый словарь:
{1: 5, 2: 6, 3: 7, 4: 8}
Оригинальный словарь после изменения:
{1: 99, 2: 6, 3: 7, 4: 8}
Новый словарь после изменения:
{1: 5, 2: 6, 3: 7, 4: 8}

Точно так же, когда мы добавляем какой-либо элемент к исходному объекту, он не будет иметь никакого влияния на новый объект.

Ниже пример:

import copy

data = {1: 5, 2: 6, 3: 7, 4: 8}
print("Оригинальный словарь:")
print(data)
new_data = copy.copy(data)
print("Новый словарь:")
print(new_data)
data[7] = 99
print("Оригинальный словарь после изменения:")
print(data)
print("Новый словарь после изменения:")
print(new_data)

Вывод программы:

Оригинальный словарь:
{1: 5, 2: 6, 3: 7, 4: 8}
Новый словарь:
{1: 5, 2: 6, 3: 7, 4: 8}
Оригинальный словарь после изменения:
{1: 5, 2: 6, 3: 7, 4: 8, 7: 99}
Новый словарь после изменения:
{1: 5, 2: 6, 3: 7, 4: 8}
Источник: https://egorovegor.ru/python-copy-deepcopy

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

Это можно увидеть в следующем примере:

import copy

data = {1: 5, 2: 6, 3: 7, 4: 8, 5: {10: 11}}
print("Оригинальный словарь:")
print(data)
new_data = copy.copy(data)
print("Новый словарь:")
print(new_data)
data[5][10] = 99
print("Оригинальный словарь после изменения:")
print(data)
print("Новый словарь после изменения:")
print(new_data)

Вывод программы:

Оригинальный словарь:
{1: 5, 2: 6, 3: 7, 4: 8, 5: {10: 11}}
Новый словарь:
{1: 5, 2: 6, 3: 7, 4: 8, 5: {10: 11}}
Оригинальный словарь после изменения:
{1: 5, 2: 6, 3: 7, 4: 8, 5: {10: 99}}
Новый словарь после изменения:
{1: 5, 2: 6, 3: 7, 4: 8, 5: {10: 99}}

При копировании объекта с помощью метода copy.copy создается только копия объекта, передаваемая в качестве параметра методу copy. Элементы внутри объекта не копируются, копируются только ссылки на элементы.

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

Но в случае вложенных объектов ссылки не изменяются, и когда мы вносим какие-либо изменения в один из объектов, они видны в другом объекте.

Как использовать deepcopy в Python?


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

Это можно сделать следующим образом:

import copy

data = {1: 5, 2: 6, 3: 7, 4: {10: 11}}
print("Оригинальный словарь:")
print(data)
new_data = copy.deepcopy(data)
print("Копия словаря:")
print(new_data)

Вывод программы:

Оригинальный словарь:
{1: 5, 2: 6, 3: 7, 4: {10: 11}}
Копия словаря:
{1: 5, 2: 6, 3: 7, 4: {10: 11}}

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

Пример ниже:

import copy

data = {1: 5, 2: 6, 3: 7, 4: {8: 9}}
print("Оригинальный словарь:")
print(data)
new_data = copy.deepcopy(data)
print("Копия словаря:")
print(new_data)
data[4][8] = 99
print("Оригинал после изменения:")
print(data)
print("Копия после изменения:")
print(new_data)

Вывод программы:

Оригинальный словарь:
{1: 5, 2: 6, 3: 7, 4: {8: 9}}
Копия словаря:
{1: 5, 2: 6, 3: 7, 4: {8: 9}}
Оригинал после изменения:
{1: 5, 2: 6, 3: 7, 4: {8: 99}}
Копия после изменения:
{1: 5, 2: 6, 3: 7, 4: {8: 9}}

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

Заключение


В этой статье вы узнали о неглубоком копировании и глубоком копировании в Python.

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