Как клонировать / копировать Python 3.x dict с внутренними ссылками на объекты - PullRequest
0 голосов
/ 10 июня 2018

У меня следующая проблема.Допустим, у нас есть класс A и класс B :

class A:

    def clone(self):

        return self.__class__()

class B:

    def __init__(self, ref):

        self.ref = ref

    def clone(self):

        return self.__class__(
            ref = self.ref
        )

У меня также есть класс, который наследуется после dict и называется Держатель .

class Holder(dict):

    def clone(self):

        return self.__class__(
            {k: v.clone() for k, v in self.items()}
        )

Теперь мне нужно как-то скопировать весь dict (со значениями, уже помещенными внутрь) с помощью моей функции clone () так, чтобы ссылки незапутаться.

А вот код, который должен прояснить поведение, которое я хочу:

original = Holder()
original['a'] = A()
original['b'] = B(original['a'])  # here we create B object 
                                  # with reference to A object

assert original['a'] is original['b'].ref  # reference is working

copy = original.clone()  # we clone our dict

assert copy['a'] is copy['b'].ref  # reference is not working like I want
                                   # copy['b'].ref points to old original['b']

assert original['a'] is not copy['a']
assert original['b'] is not copy['b']
assert original['b'].ref is not copy['b'].ref

Вот некоторые предпосылки проблемы, описанной ниже:

Допустим, у меня есть класс с именем MyClass и метакласс с именем MyClassMeta .

Я хочу предоставить функцию __ prepare __ для MyClassMeta с моим собственным dict , который будет экземпляром класса с именем Holder .Во время создания класса я буду хранить значения определенных типов во внутреннем dict из Holder экземпляра (аналогично тому, что делает EnumMeta).Поскольку экземпляр Holder будет заполнен значениями при создании класса, все экземпляры MyClass будут иметь ссылку на один и тот же объект.

Теперь я хочуиметь отдельную копию для каждого экземпляра моего держателя .Я думал, что я могу просто скопировать / клонировать мой объект, но проблема возникла, когда я добавил объект, который ссылался на другой объект внутри того же самого dict.

1 Ответ

0 голосов
/ 11 июня 2018

Правильный способ клонировать пользовательские структуры данных в Python - реализовать специальный метод __deepcopy__.Это то, что вызывается функцией copy.deepcopy.

Как объясняется в doc :

Часто существуют две проблемы с операциями глубокого копирования, которые неt существует с мелкими операциями копирования:

  • Рекурсивные объекты (составные объекты, которые прямо или косвенно содержат ссылку на себя) могут вызвать рекурсивный цикл.
  • Поскольку глубокое копирование копирует всеон может копировать слишком много, например, данные, которые предназначены для совместного использования копиями. [Это проблема, с которой вы столкнулись]

Функция deepcopy() позволяет избежать этих проблем:

  • , сохраняя словарь «памятки»объекты, уже скопированные во время текущего прохода копирования;и
  • позволяя пользовательским классам переопределять операцию копирования или набор копируемых компонентов.

Код

import copy

class A:
    def __deepcopy__(self, memo):
        return self.__class__()

class B:
    def __init__(self, ref):
        self.ref = ref

    def __deepcopy__(self, memo):
        return self.__class__(
            ref=copy.deepcopy(self.ref, memo)
        )

class Holder(dict):
    def __deepcopy__(self, memo):
        return self.__class__(
            {k: copy.deepcopy(v, memo) for k, v in self.items()}
        )

Тест

import copy

original = Holder()
original['a'] = A()
original['b'] = B(original['a'])  # here we create B object
                                  # with reference to A object

assert original['a'] is original['b'].ref  # reference is working

cp = copy.deepcopy(original)  # we clone our dict

assert cp['a'] is cp['b'].ref  # reference is still working

assert original['a'] is not cp['a']
assert original['b'] is not cp['b']
assert original['b'].ref is not cp['b'].ref
...