Какова внутренняя реализация `copy.deepcopy()` в Python и как правильно переопределить `__deepcopy__()`?

Читая ответ Энтони Хачкинса на вопрос «Как переопределить операции копирования/глубокого копирования для объекта 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 ()?но никто из них не дал исчерпывающего ответа и обоснования.

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
3
0
75
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

(Справочная) реализация copy.deepcopy находится здесь

Как видите, первое, что делает функция, — это проверяет экземпляр в memo, поэтому нет необходимости проверять свою собственную реализацию.


Вот описание того, как работает эта функция:

deepcopy(x, memo=None)

  1. проверяет, находится ли x в memo. Если это так, верните связанное с ним значение.

  2. пытается определить метод копирования в указанном порядке

    1. ищу это в словаре _deepcopy_dispatch
    2. проверка наличия у x метода __deepcopy__ и его использование
    3. проверяем, можно ли его уменьшить (см. здесь). Т.е. если его можно мариновать. В этом случае он запускает это, копирует уменьшенный объект, а затем распаковывает его.
  3. запускает найденный метод для создания копии

  4. регистрирует эту копию в заметке.

(Некоторые детали я упускаю, читайте код, если они вам интересны)

Итак, чтобы ответить на ваши вопросы (и другие, которые могут у вас возникнуть):

    • Вопрос: что произойдет, если вы переопределите __deepcopy__
    • О: Он вызывается на шаге 3 вместо метода по умолчанию (если только в словаре _deepcopy_dispatch нет метода, но этот словарь должен содержать только методы для базовых типов).
    • Вопрос: когда происходит рекурсивность
    • О: Это происходит, когда вызывается ваша функция __deepcopy__. Этот вызов должен рекурсивно вызывать deepcopy с тем же словарем заметок.
    • Вопрос: Почему реализация Энтони Хачкинса регистрирует экземпляр в memo, если функция deepcopy тоже это делает (шаг 4)
    • О: потому что deepcopy регистрирует объект в memo в самом конце, тогда как, чтобы избежать бесконечной рекурсии, вам нужно зарегистрировать его перед выполнением рекурсивных вызовов.

Примечание: Для более простого способа копирования ваших пользовательских классов вы также можете реализовать методы __gestate__ и __setstate__ и полагаться на тот факт, что deepcopy использует методы травления.

Спасибо за указание на существование такой проверки, которая отвечает на главный вопрос моего вопроса. Тем не менее, мне не терпится увидеть более подробный взгляд на внутреннюю работу deepcopy() и на наличие других особенностей при переопределении __deepcopy()__.

Tom Lin 07.07.2024 13:06

@TomLin Я отредактировал ответ, включив подробную информацию о том, как работает эта функция. Я пропускаю некоторые детали, если хотите подробнее, рекомендую прочитать реализацию

tbrugere 07.07.2024 14:26

Другие вопросы по теме