Правильный способ добавления пользовательской (глубокой) логики копирования в класс python

Я реализую класс python, который предоставляет некоторую вложенную структуру данных. Я хочу добавить поддержку для копирования через copy.copy() и глубокое копирование через copy.deepcopy(), которое, как описывают docs для модуля копирования, включает в себя специальные методы __copy__() и __deepcopy__.

Я знаю, как научить мой класс делать копию самого себя, но я хочу, чтобы не пройти через __init__() в новом экземпляре, так как __init__() выполняет некоторые действия, которые моя копирующая логика не хочет (или не нуждается) сделать.

в чем я оказался, это этот метод, который работает по назначению:

def __copy__(self):
    cls = type(self)
    obj = cls.__new__(cls)
    # custom copying logic that populates obj goes here
    return obj

мой вопрос: вызывает cls.__new__(cls) правильный подход для реализации __copy__(), который хочет пропустить __init__() для копии? или есть ли более "питонический" подход, который я упускал из виду?

Ответ 1

Я не знаю, является ли это более питоническим, но вы можете использовать флаг.

from collections import Mapping
from copy import copy, deepcopy


class CustomDict(dict, Mapping):
    _run_setup = True

    def __init__(self, *args, **kwargs):
        self._dict = dict(*args, **kwargs)
        if args and isinstance(args[0], CustomDict):
            self._run_setup = args[0]._run_setup
        if self._run_setup:
            print("Doing some setup stuff")
        else:
            try:
                print("Avoiding some setup stuff")
            finally:
                self._run_setup = True

    def __getitem__(self, key):
        return self._dict[key]

    def __iter__(self):
        return iter(self._dict)

    def __len__(self):
        return len(self._dict)

    def __copy__(self):
        self._run_setup = False
        try:
            copied_custom_dict = CustomDict(self)
        finally:
            self._run_setup = True
        return copied_custom_dict

В приведенном выше __init__ материал условной настройки выполняется только, если _run_setup = True. Единственный способ избежать этого - вызвать CustomDict, причем первым параметром является экземпляр самого себя с _run_setup = False. Таким образом, легко переключаться с переключателем установки различными способами.

Блоки try...finally выглядят неуклюжими для меня, но это способ убедиться, что каждый метод запускается и заканчивается на _run_setup = True.