При вызове copy.copy для экземпляра пользовательского класса что изменится, если в классе определить __copy__?
Если у пользовательского класса определён __copy__, модуль copy вызывает этот метод и использует возвращённый объект как результат поверхностного копирования. Метод __copy__ сам отвечает за корректное создание копии; вложенные объекты автоматически не копируются.
Если __copy__ отсутствует, copy.copy применяет внутренние оптимизированные обработчики для некоторых типов или использует протокол редукции объекта, включающий __reduce_ex__ либо __reduce__.
Присваивание имени объекту в Python не создаёт копию: оно лишь добавляет ещё одну ссылку на тот же объект. Универсальное копирование невозможно выполнить одинаково для всех экземпляров, поскольку объект может содержать внешние ресурсы, кэш, дескрипторы, изменяемые коллекции или инварианты.
Поэтому модуль copy предоставляет объектам специальные точки расширения. __copy__ позволяет классу явно определить семантику поверхностной копии, не заставляя модуль угадывать структуру объекта.
Поверхностная копия должна создать новый внешний объект, но обычно сохранить ссылки на его вложенные значения. Неверная реализация может либо неожиданно разделить состояние между копиями, либо скопировать слишком много и нарушить смысл объекта.
Особенно опасно полагаться на автоматический механизм для классов, содержащих блокировки, файловые дескрипторы, сетевые соединения или кэшированные данные. Такие объекты могут быть некопируемыми или требовать специальной инициализации.
При вызове copy.copy(obj) для экземпляра пользовательского класса модуль copy ищет специальный метод __copy__. Если он найден, метод вызывается без аргументов экземпляра-копии и должен вернуть результат копирования.
В примере создаётся новый Cart, но список items остаётся общим. Это и есть поверхностное копирование. Если список тоже нужно копировать, реализация должна явно вызвать copy.copy для него или выбрать другой способ создания состояния.
__copy__ не получает специальный словарь уже скопированных объектов. Такой аргумент используется у __deepcopy__(memo), который необходим для рекурсивного копирования графов объектов, сохранения общих ссылок и обработки циклов.
При отсутствии __copy__ модуль сначала может применить зарегистрированный быстрый обработчик для конкретного встроенного типа. Для остальных объектов используется более общий протокол редукции, основанный на __reduce_ex__ или __reduce__. Это запасной механизм, а не требование реализовывать оба метода вручную.
Возвращать из __copy__ обязательно нужно объект с подходящей семантикой. Возврат исходного объекта технически возможен, но обычно корректен только для действительно неизменяемых объектов или объектов, безопасно разделяющих состояние.
В библиотеке есть объект конфигурации с изменяемым словарём параметров и внутренним кэшем. Обычная поверхностная копия может разделить словарь и привести к тому, что изменение конфигурации одного экземпляра повлияет на другой. Полная копия решит эту проблему, но может без необходимости продублировать большой кэш.
Рассматривались два варианта:
copy.deepcopy — проще, но дороже и потенциально опасно для объектов, которые нельзя безопасно глубоко копировать;__copy__ вручную — требует явно описать правила разделения состояния, но даёт предсказуемый результат.Выбран __copy__: основные параметры копируются, а неизменяемый общий кэш инициализируется или разделяется согласно контракту класса. Это сохраняет инварианты объекта и не создаёт лишние копии больших структур.
__copy__ словарь memo, как в __deepcopy__?Нет. Сигнатура __copy__ не предусматривает memo. Он предназначен для поверхностной копии, где модуль не обязан рекурсивно обходить весь граф объектов. Для глубокой копии используется __deepcopy__(memo), причём переданный словарь позволяет избежать циклов и сохранить идентичность повторно встречающихся объектов.
__copy__?Нет. __copy__ получает исходный объект и сам определяет, какие поля копировать, а какие разделять. Если вернуть новый объект с теми же ссылками на вложенные коллекции, эти коллекции будут общими; для независимого состояния их нужно явно скопировать.
__copy__ исходный объект?Да, это допустимо с точки зрения протокола, но безопасно только при подходящей семантике. Такой вариант может быть разумен для неизменяемого значения, например объекта, состояние которого нельзя изменить после создания. Для изменяемого объекта он нарушает ожидаемое свойство копирования: изменение результата неожиданно изменит исходный экземпляр.