В чем разница между copy и deepcopy в Python и зачем они нужны
Что проверяют
Понимаете ли вы модель данных Python: переменная это ссылка на объект, а копирование бывает разной глубины. Следом обычно просят предсказать вывод короткого фрагмента со вложенным списком или спрашивают, как научить свой класс правильно копироваться.
Как отвечать
Начните с того, что присваивание b = a ничего не копирует: обе переменные смотрят на один объект. Дальше два уровня:
copy.copy(): поверхностная копия. Создаётся новый контейнер, но внутри лежат те же самые объекты. Для списка это то же, чтоlst[:],list(lst)илиlst.copy(), для словаряdict.copy();copy.deepcopy(): рекурсивно копирует всё дерево вложенных объектов. Внутри держит словарьmemo, поэтому переживает циклические ссылки, а объект, на который ссылаются дважды, копирует один раз;- неизменяемые объекты: числа, строки,
tupleиз неизменяемых элементов не копируются вовсе, возвращается тот же объект, и это безопасно; - свои классы: поведение настраивается через
__copy__и__deepcopy__(self, memo), а по умолчанию работает протокол__reduce_ex__, тот же, что уpickle.
Пример на пальцах: a = [[1, 2], [3]], b = copy.copy(a). Вызов b.append([4]) оригинал не трогает, а b[0].append(9) меняет и a, потому что внутренний список общий.
Зачем это нужно: защитить входные данные функции от изменения, сделать снимок состояния перед экспериментом, размножить шаблон конфигурации. При этом deepcopy дорогой: он обходит весь граф объектов и заметно медленнее поверхностной копии, поэтому в горячем коде его избегают.
Где спотыкаются
Думают, что copy() копирует «всё». Потом вложенный словарь конфигурации меняется сразу у всех, кто получил «копию».
Создают матрицу как [[0] * 3] * 3 и удивляются, что запись в одну строку меняет все три. Это та же история с общими ссылками: внешний список хранит три ссылки на один внутренний. Правильно [[0] * 3 for _ in range(3)].
Применяют deepcopy к объектам с ресурсами: соединению с базой, файлу, блокировке. Такие объекты либо падают с TypeError при копировании, либо копируются бессмысленно, и для них нужен свой __deepcopy__.