使用python deepcopy时的AttributeError

时间:2017-09-18 16:03:33

标签: python deep-copy cyclic-reference

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

Python docs看起来好像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'
UNIQUE CONSTRAINT

1 个答案:

答案 0 :(得分:5)

当您执行以下操作时,循环依赖关系是deepcopy的问题:

  1. 具有必须经过哈希处理且包含引用周期的类,并且
  2. 不确保在对象构造中建立与哈希相关(和相关的相关)不变量,而不仅仅是初始化
  3. 问题是取消对象(deepcopy,默认情况下,通过pickling和unpickling复制自定义对象,除非定义了特殊的__deepcopy__方法)创建空对象而不初始化它,然后尝试逐个填写其属性。当它尝试填写node1的属性时,需要初始化node2,而node1又依赖于部分创建的edge_dict(在两种情况下都归因于edge_dict )。当它尝试填充一个Node的{​​{1}}时,添加到Node的{​​{1}}尚未设置其edge_dict属性,所以哈希的尝试失败了。

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

    __getnewargs_ex__

    注意:如果这是Python 2代码,请确保明确继承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 ... 并将object更改为super()中的super(Node, cls);给出的代码是更简单的Python 3代码。

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

    copy