Копирование объекта в Python подразумевает создание нового объекта, имеющий другой идентификатор (следовательно, это совсем другой объект) и одно и то же содержимое. Копирование позволяет работать с объектом, сохраняя при этом исходный. Тема копирования объектом нуждается в детальном рассмотрении. В этой статье мы рассмотрим два вида копирования, которые используются в Python, — поверхностное и глубокое.
Копирование неизменяемых объектов
Повторим, неизменяемый объект (immutable) существует в одном экземпляре и изменить его нельзя. О копировании неизменяемых объектах говорить не имеет особого смысла.
Если ваш объект неизменяемый, то, чтобы получить экземпляр, который имеет то же самое содержимое, просто используйте присвоение. Пример кода на Python:
string = "Hello, world!" string_ = string # Это два разных объекта, несвязанных друг с другом
Поверхностное копирование в Python
Многие изменяемые объекты могут содержать другие изменяемые объекты. Таким образом, существуют два типа копий:
- поверхностное (shallow copy),
- глубокое (deep copy).
Они по разному действуют при встрече с изменяемыми объектами внутри объекта, который нужно скопировать. Рассмотрим сначала поверхностное копирование.
Разработка и внедрение ML-решений
Код курса
MLOPS
Ближайшая дата курса
11 ноября, 2024
Продолжительность
24 ак.часов
Стоимость обучения
54 000 руб.
Списки и словари имеют метод copy
, который возвращает поверхностные копии. Например:
>>> sublist = [] >>> outer_list = [42, 73, sublist] >>> copy_list = outer_list.copy()
Итак, у нас есть список, который содержит другой список. Затем мы создаем копию с помощью метода copy
. Вопрос: полученная копия и исходный список — это разные объекты? Ответ — да:
>>> copy_list is outer_list False >>> copy_list[0] = 0 >>> outer_list # Не повлияло на исходный [42, 73, []]
А теперь попробуем изменить содержимое списка sublist
и посмотрим, что случится:
>>> sublist.append(999) >>> copy_list [0, 73, [999]] >>> outer_list [42, 73, [999]]
Изменение содержимого вложенного списка повлекло за собой изменение как copy_list
, так и outer_list
. Такое поведение очень не очевидно. Казалось бы, эти два объекта различны, но изменение вложенного списка поменял исходный объект и копию. Такие копии называются поверхностными.
На самом деле модификация вложенного списка не изменяет содержимое ни copy_list
, ни outer_list
. Просто третий элемент обоих объектов связан с другим списком, который в свою очередь может быть модифицирован. В терминах языка Си мы бы говорили о наличие указателя.
Иногда такое поведение нам не требуется: мы хотим, чтобы объекты были абсолютно независимыми. Но об этом чуть позже, а сейчас рассмотрим о других способах создания поверхностных копий.
Как ещё можно создавать поверхностные копии
Работая со списками, часто приходится выполнять срезы (slicing, о котором мы говорили тут). Для создания поверхностной копии можно использовать данную технику:
>>> outer_list = [42, 73, []] >>> shallow_copy = outer_list[::] >>> outer_list[2].append(999) >>> shallow_copy [42, 73, [999]]
Собственно мы получаем тот же самый результат, что и при вызове copy
, т.е. создается поверхностная копия.
Или пример создания списка списка:
>>> outer_list = [42, 73, []] >>> shallow_copy = list(outer_list) >>> shallow_copy[2].append(999) >>> outer_list [42, 73, [999]]
— который приводит к тому же результату. То же самое можно применить и к словарю:
>>> outer_dict = {42: 73, "list": []} >>> shallow_copy = dict(outer_dict) >>> outer_dict["list"].append(999) >>> shallow_copy {42: 73, 'list': [999]}
Глубокое копирование в Python
Если вам требуется копирование такие копии, внутренние объекты которых не связаны с чем-то внешним, то вам понадобится глубокое копирование.
Глубокое копирование похоже на рекурсивный алгоритм: копируются элементы первого уровня, и если был найден изменяемый объект, то заходим в глубь этого объекта и копируем его содержимое. Ниже приведен пример того, как это можно реализовать.
def list_deepcopy(l): return [ elem if not isinstance(elem, list) else list_deepcopy(elem) for elem in l ]
Мы теперь можем воспользоваться этой функцией для копирования списка:
>>> sublist = [] >>> outer_list = [42, 73, sublist] >>> copy_list = list_deepcopy(outer_list) >>> sublist.append(73) >>> copy_list [42, 73, []] >>> outer_list [42, 73, [73]]
Как можно увидеть, изменение списка sublist
не повлекло за собой изменение копии.
Для больших списков такая функция не будет работать с приемлемой скоростью, но к счастью в Python есть модуль, который реализуется и поверхностное, и глубокое копирование.
Модуль copy и функция deepcopy
Стандартный Python-модуль copy имеет две нужные нам функции [2]:
copy
для поверхностного копирования;deepcopy
для глубокого.
Функция deepcopy
достаточно умна, чтобы справляться с возникающими трудностями, которые могут возникнуть при копировании. Можете даже взглунять на количество проверок в исходном коде.
Если вы пишите со,ственные объекты и хотите указать, какое копирование использовать, то вам нужно реализовать протоколы __copy__
и/или __deepcopy__
.
О том, как происходит копирование данных большого размера (Big Data) вы узнаете на наших образовательных курсах в лицензированном учебном центре обучения и повышения квалификации руководителей и ИТ-специалистов (менеджеров, архитекторов, инженеров, администраторов, Data Scientist’ов и аналитиков Big Data) в Москве: