我有一个重写了 __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
当你:
- 具有必须散列并包含循环引用的类,以及
- 不要确保在对象构造时建立哈希相关(和相等性相关)不变量,而不仅仅是初始化
问题是 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/