python - 使用 python deepcopy 时出现 AttributeError

标签 python deep-copy cyclic-reference

我有一个重写了 __eq____hash__ 的类,以使其对象充当字典键。每个对象还带有一个字典,由同一类的其他对象作为键。当我尝试 deepcopy 整个结构时,我得到了一个奇怪的 AttributeError。我在 OsX 上使用 Python 3.6.0。

来自 Python docs看起来 deepcopy 使用 memo 字典来缓存它已经复制的对象,所以嵌套结构应该不是问题。那我做错了什么?我应该编写自己的 __deepcopy__ 方法来解决这个问题吗?怎么办?

from copy import deepcopy


class Node:

    def __init__(self, p_id):
        self.id = p_id
        self.edge_dict = {}
        self.degree = 0

    def __eq__(self, other):
        return self.id == other.id

    def __hash__(self):
        return hash(self.id)

    def add_edge(self, p_node, p_data):
        if p_node not in self.edge_dict:
            self.edge_dict[p_node] = p_data
            self.degree += 1
            return True
        else:
            return False

if __name__ == '__main__':
    node1 = Node(1)
    node2 = Node(2)
    node1.add_edge(node2, "1->2")
    node2.add_edge(node1, "2->1")
    node1_copy = deepcopy(node1)

File ".../node_test.py", line 15, in __hash__
    return hash(self.id)
AttributeError: 'Node' object has no attribute 'id'

最佳答案

循环依赖是一个问题 deepcopy当你:

  1. 具有必须散列并包含循环引用的类,以及
  2. 不要确保在对象构造时建立哈希相关(和相等性相关)不变量,而不仅仅是初始化

问题是 unpickling 对象( deepcopy ,默认情况下,通过 pickling 和 unpickling 复制自定义对象,除非定义了特殊的 __deepcopy__ 方法)创建空对象而不初始化它,然后尝试填充它的属性逐个。当它尝试填写 node1 时的属性,需要初始化node2 ,这又依赖于部分创建的 node1 (在这两种情况下都是由于 edge_dict )。当时它正在尝试填写 edge_dict一个Node , Node它正在添加到 edge_dict没有它的 id尚未设置属性,因此对其进行散列的尝试失败。

您可以使用 __new__ 更正此问题确保在初始化可变的、可能递归的属性和定义 pickle 之前建立不变量助手 __getnewargs__ (或 __getnewargs_ex__ )使其正确使用它们。具体来说,将您的类定义更改为:

class Node:
    # __new__ instead of __init__ to establish necessary id invariant
    # You could use both __new__ and __init__, but that's usually more complicated
    # than you really need
    def __new__(cls, p_id):
        self = super().__new__(cls)  # Must explicitly create the new object
        # Aside from explicit construction and return, rest of __new__
        # is same as __init__
        self.id = p_id
        self.edge_dict = {}
        self.degree = 0
        return self  # __new__ returns the new object

    def __getnewargs__(self):
        # Return the arguments that *must* be passed to __new__
        return (self.id,)

    # ... rest of class is unchanged ...

注意:如果这是 Python 2 代码,请确保显式继承自 object并更改 super()super(Node, cls)__new__ ;给出的代码是更简单的 Python 3 代码。

处理的备用解决方案copy.deepcopy , 不支持酸洗或要求使用 __new__/__getnewargs__ (需要新式类)将仅覆盖深度复制。您将在原始类上定义以下额外方法(并确保模块导入 copy ),否则保持不变:

def __deepcopy__(self, memo):
    # Deepcopy only the id attribute, then construct the new instance and map
    # the id() of the existing copy to the new instance in the memo dictionary
    memo[id(self)] = newself = self.__class__(copy.deepcopy(self.id, memo))
    # Now that memo is populated with a hashable instance, copy the other attributes:
    newself.degree = copy.deepcopy(self.degree, memo)
    # Safe to deepcopy edge_dict now, because backreferences to self will
    # be remapped to newself automatically
    newself.edge_dict = copy.deepcopy(self.edge_dict, memo)
    return newself

关于python - 使用 python deepcopy 时出现 AttributeError,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46283738/

相关文章:

C++ 错误 : forward declaration of 'struct. ..?

java - 循环依赖——总是错的?

c++ - 新图的深拷贝构造函数

java - 在构造函数中复制矩阵时的行为非常奇怪

Python Gtk TextView 在末尾插入文本

python - OpenCV:如何正确应用morphologyEx操作?

c# - 为什么.NET 框架不提供深度复制对象的方法?

SQL SELECT 在父亲 ID 组织树中查找循环引用?

目标搜索算法的python优化

python - 大 O 包含两个相乘的变量