Поверхностная и глубокая копия объектов Python
Присваивание в Python не копирует объект: оно создаёт ещё одно имя для той же ссылки. Поверхностная копия создаёт новый внешний контейнер, но оставляет ссылки на вложенные…
Присваивание в Python не копирует объект: оно создаёт ещё одно имя для той же ссылки. Поверхностная копия создаёт новый внешний контейнер, но оставляет ссылки на вложенные элементы. Глубокая копия рекурсивно копирует составные части и учитывает уже посещённые объекты. Выбор определяется не размером записи, а моделью владения: какие изменения должны быть общими, а какие требуется изолировать.
Три разных операции
После alias = original выражение alias is original истинно. После copy.copy(original) внешний объект новый, но его изменяемые дети могут совпадать по идентичности. После copy.deepcopy(original) обычно новыми становятся и вложенные изменяемые объекты. Неизменяемые значения могут безопасно переиспользоваться даже при глубоком копировании, поэтому не следует требовать новый идентификатор для каждого числа или строки.
Рабочий пример с вложенным списком
from copy import copy, deepcopy
original = {
'owner': 'Ирина',
'tasks': [
{'title': 'тесты', 'done': False},
{'title': 'документация', 'done': False},
],
}
shallow = copy(original)
deep = deepcopy(original)
shallow['tasks'][0]['done'] = True
deep['tasks'][1]['done'] = True
print(original['tasks'][0]['done'])
print(original['tasks'][1]['done'])
print(shallow['tasks'] is original['tasks'])
print(deep['tasks'] is original['tasks'])Ожидаемый результат и объяснение
True
False
True
FalseСловарь shallow новый, однако значение по ключу tasks осталось той же ссылкой. Изменение первого вложенного словаря видно через original. У deep список и словари внутри отделены, поэтому изменение второй задачи не затрагивает источник. Простое выражение dict(original) в этом примере тоже создало бы лишь поверхностную копию.
Циклы, общие ссылки и настройка deepcopy
deepcopy хранит служебную таблицу уже скопированных объектов. Благодаря этому он умеет обходить циклические структуры и сохранять совместное использование: если две ссылки исходного графа указывали на один дочерний объект, соответствующие ссылки копии также должны указывать на один новый дочерний объект. Класс может настроить поведение методом __deepcopy__(self, memo), но тогда обязан корректно использовать memo, иначе легко получить рекурсию или разрушить структуру связей.
Как найти разделяемую вложенную ссылку
Типичная логическая ошибка выглядит не как исключение: разработчик делает copy.copy, меняет вложенный список и замечает изменение оригинала. Для диагностики временно сравните идентичности на каждом уровне: shallow is original, затем shallow['tasks'] is original['tasks'], затем идентичность конкретной задачи. Первый уровень покажет False, второй — True, что точно укажет место разделяемой ссылки.
Обратная ошибка — бездумно применять deepcopy к объектам, представляющим файлы, соединения или внешние ресурсы. Не каждый объект допускает осмысленное копирование. В такой модели лучше явно создать новый доменный объект из нужных данных либо запретить операцию.
Практикум: три копии графа проектов
Создайте список проектов, где каждый проект содержит имя и список участников. Получите псевдоним, поверхностную и глубокую копии. Добавьте участника во вложенный список каждой версии по очереди и запишите, где видно изменение. Самостоятельная проверка: используйте is на внешних списках, словарях проекта и списках участников; перед запуском предскажите каждое значение, а затем объясните результат через граф ссылок, не через фразу «Python иногда копирует».
Границы поверхностного и глубокого копирования
Присваивание разделяет весь объект. Поверхностная копия отделяет только внешний уровень. Глубокая копия обходит граф и отделяет вложенное изменяемое состояние, но может быть дорогой или концептуально неверной для ресурсов. Сначала определите границу владения данными, затем выбирайте способ копирования.
Вопросы о копировании объектов
Срез списка создаёт глубокую копию?
Нет. items[:] создаёт новый список с теми же ссылками на элементы, то есть поверхностную копию.
Всегда ли deepcopy создаёт новый объект для каждого значения?
Нет. Неизменяемые объекты могут переиспользоваться, а общие ссылки внутри графа сохраняют общность уже в копии.
Можно ли заменить deepcopy сериализацией?
Не в общем случае. Сериализация поддерживает ограниченный набор данных, может терять типы и имеет собственные правила восстановления. Это отдельная операция с другим контрактом.