首页 > 解决方案 > 如何使用对对象的内部引用来克隆/深度复制 Python 3.x dict

问题描述

我有以下问题。假设我们有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之后继承的类,称为Holder

class Holder(dict):

    def clone(self):

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

现在我想要的是用我的 clone() 函数以某种方式复制整个 dict (其中已经放入了值),这样引用就不会被弄乱。

这里有一些代码应该澄清我想要的行为:

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的元类。

我想为MyClassMeta的__prepare__函数提供我自己的字典,该字典将是名为Holder的类的实例。在创建类期间,我会将某些类型的值存储到Holder实例的内部字典中(类似于 EnumMeta 所做的)。由于在类创建期间Holder实例将被填充值,因此MyClass的所有实例都将具有对同一对象的引用。

现在我想要的是每个我的Holder实例都有单独的副本。我以为我可以复制/克隆我的对象,但是当我在同一个字典中添加引用其他对象的对象时出现了问题。

标签: python-3.xdictionarycopyclonemetaclass

解决方案


在 Python 中克隆自定义数据结构的正确方法是实现__deepcopy__特殊方法。这就是copy.deepcopy函数调用的内容。

文档中所述:

深拷贝操作通常存在两个浅拷贝操作不存在的问题:

  • 递归对象(直接或间接包含对其自身的引用的复合对象)可能会导致递归循环。
  • 因为深拷贝复制了它可能复制过多的所有内容,例如打算在副本之间共享的数据。【这就是你面临的问题】

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

推荐阅读