Читая ответ Энтони Хачкинса на вопрос «Как переопределить операции копирования/глубокого копирования для объекта Python?», меня смущает, почему его реализация __deepcopy()__
не проверяет memo
сначала, скопирован ли уже текущий объект, прежде чем копировать текущий объект. На это также указывает в комментарии Антонин Хошковец. Комментарий Джонатана Х также затронул эту проблему и упомянул, что copy.deepcopy()
, похоже, прерывает вызов __deepcopy()__
, если объект уже был скопирован ранее. Однако он не указывает четко, где это делается в коде модуля copy
.
Чтобы проиллюстрировать проблему отсутствия проверки memo
, предположим, что объект a
ссылается на b
и c
, а оба объекта b
и c
ссылаются на объект d
. Во время глубокого копирования a
объект d
следует копировать только один раз во время копирования b
или c
, в зависимости от того, что наступит раньше.
По сути, я спрашиваю, почему ответ Энтони Хачкинса не делает следующее:
from copy import deepcopy
class A:
def __deepcopy__(self, memo):
# Why not add the following two lines?
if id(self) in memo:
return memo[id(self)]
cls = self.__class__
result = cls.__new__(cls)
memo[id(self)] = result
for k, v in self.__dict__.items():
setattr(result, k, deepcopy(v, memo))
return result
Поэтому было бы здорово, если бы кто-нибудь мог объяснить внутреннюю реализацию deepcopy()
в модуле copy
, чтобы продемонстрировать передовую практику переопределения __deepcopy__
, а также просто дать мне знать, что происходит под капотом.
Я бегло просмотрел исходный код copy.deepcopy()
, но меня смутили такие вещи, как copier
, reductor
и _reconstruct()
. Я читал такие ответы, как разъяснение переопределения deepcopy и В Python, как я могу вызвать copy.deepcopy в моей реализации deepcopy ()?но никто из них не дал исчерпывающего ответа и обоснования.
(Справочная) реализация copy.deepcopy
находится здесь
Как видите, первое, что делает функция, — это проверяет экземпляр в memo
, поэтому нет необходимости проверять свою собственную реализацию.
Вот описание того, как работает эта функция:
deepcopy(x, memo=None)
проверяет, находится ли x
в memo
. Если это так, верните связанное с ним значение.
пытается определить метод копирования в указанном порядке
_deepcopy_dispatch
x
метода __deepcopy__
и его использованиезапускает найденный метод для создания копии
регистрирует эту копию в заметке.
(Некоторые детали я упускаю, читайте код, если они вам интересны)
Итак, чтобы ответить на ваши вопросы (и другие, которые могут у вас возникнуть):
__deepcopy__
_deepcopy_dispatch
нет метода, но этот словарь должен содержать только методы для базовых типов).__deepcopy__
. Этот вызов должен рекурсивно вызывать deepcopy
с тем же словарем заметок.deepcopy
тоже это делает (шаг 4)deepcopy
регистрирует объект в memo
в самом конце, тогда как, чтобы избежать бесконечной рекурсии, вам нужно зарегистрировать его перед выполнением рекурсивных вызовов.Примечание:
Для более простого способа копирования ваших пользовательских классов вы также можете реализовать методы __gestate__
и __setstate__
и полагаться на тот факт, что deepcopy использует методы травления.
@TomLin Я отредактировал ответ, включив подробную информацию о том, как работает эта функция. Я пропускаю некоторые детали, если хотите подробнее, рекомендую прочитать реализацию
Спасибо за указание на существование такой проверки, которая отвечает на главный вопрос моего вопроса. Тем не менее, мне не терпится увидеть более подробный взгляд на внутреннюю работу
deepcopy()
и на наличие других особенностей при переопределении__deepcopy()__
.