PythonMiddleTechnical
Объясните разницу между поверхностным (shallow copy) и глубоким (deep copy) копированием.
Shallow copy создаёт новый контейнер со ссылками на те же вложенные объекты; deepcopy рекурсивно клонирует всю иерархию. Используйте deepcopy, когда копия должна быть полностью независима от оригинала.
Shallow copy и deep copy в Python
Python предоставляет два механизма копирования объектов через модуль copy: copy.copy() для поверхностного и copy.deepcopy() для глубокого копирования.
Поверхностное копирование (shallow copy)
Создаётся новый объект-контейнер, но вложенные объекты не копируются — в новом контейнере хранятся ссылки на те же вложенные объекты, что и в оригинале.
import copy
original = [[1, 2, 3], [4, 5, 6]]
shallow = copy.copy(original)
shallow[0].append(99) # меняем вложенный список
print(original[0]) # [1, 2, 3, 99] — оригинал тоже изменился!
print(shallow is original) # False — разные контейнеры
print(shallow[0] is original[0]) # True — одни и те же вложенные объекты
Глубокое копирование (deep copy)
Рекурсивно создаёт новые объекты для всей иерархии. Изменение любой части копии не затрагивает оригинал.
import copy
original = [[1, 2, 3], [4, 5, 6]]
deep = copy.deepcopy(original)
deep[0].append(99)
print(original[0]) # [1, 2, 3] — оригинал не изменился
print(deep[0] is original[0]) # False — разные объекты
Встроенные способы создания shallow copy
Многие встроенные типы поддерживают поверхностное копирование без импорта copy:
lst = [1, [2, 3]]
# Все три эквивалентны shallow copy для списков:
a = lst[:]
b = list(lst)
c = lst.copy()
# Для словарей:
d = {"a": [1, 2]}
d_copy = d.copy() # shallow
# Для множеств:
s = {1, 2, 3}
s_copy = s.copy() # shallow (но элементы множества должны быть immutable)
Когда что применять
- Shallow copy — когда вложенные объекты иммутабельны (числа, строки, кортежи без mutable элементов) или когда намеренно нужна общая ссылка на вложенную структуру.
- Deep copy — когда нужна полная независимость копии: конфигурационные объекты, состояние игры, дерево разбора AST.
Кастомизация поведения
Класс может контролировать оба режима через специальные методы:
import copy
class MyConfig:
def __init__(self, data: dict, secret: str):
self.data = data
self._secret = secret # не хотим копировать
def __copy__(self):
# shallow: новый объект, data — та же ссылка
new = MyConfig.__new__(MyConfig)
new.data = self.data
new._secret = self._secret
return new
def __deepcopy__(self, memo: dict):
new = MyConfig.__new__(MyConfig)
memo[id(self)] = new
new.data = copy.deepcopy(self.data, memo)
new._secret = self._secret # намеренно не копируем
return new
cfg = MyConfig({"key": [1, 2]}, "s3cr3t")
cfg_deep = copy.deepcopy(cfg)
print(cfg.data is cfg_deep.data) # False
print(cfg._secret is cfg_deep._secret) # True (строки — интернированы, ок)
Подводные камни
- Циклические ссылки:
deepcopyобрабатывает их через внутренний словарьmemo, но самописные рекурсивные функции копирования зависнут. Всегда передавайтеmemoв рекурсивных__deepcopy__. - Иллюзия безопасности tuple:
copy.copy((list1, list2))возвращает тот же объект кортежа (CPython оптимизация), а вложенные списки не скопированы. - Производительность deepcopy: на больших вложенных структурах работает значительно медленнее — профилируйте перед применением в горячих путях.
- Не все объекты копируемы: сокеты, файловые дескрипторы, потоки, функции с замыканиями на нескопируемые ресурсы поднимут
TypeError. - dataclasses и copy:
dataclasses.replace()делает shallow copy только указанных полей — это не то же самое, чтоcopy.copy(). - NumPy arrays:
arr.copy()всегда делает deep copy данных массива, но объектные массивы (dtype=object) содержат ссылки — нуженdeepcopy. - pickle vs deepcopy:
pickle.loads(pickle.dumps(obj))тоже даёт deep copy, но только для pickle-сериализуемых объектов;deepcopyработает шире.
Common mistakes
- Описывать shallow vs deep copy только как термин и не показывать механизм на минимальном примере.
- Игнорировать ошибки, пустые данные, конкурентный доступ или границы транзакции.
- Не связывать поведение с официальным контрактом Python и реальной эксплуатацией.
What the interviewer is testing
- Объясняет shallow vs deep copy через последовательность действий, а не через набор ключевых слов.
- Приводит короткий кодовый пример или production-сценарий с ожидаемым поведением.
- Называет хотя бы один риск: производительность, безопасность, транзакции, память или сопровождение.