考虑以下MWE:
#import dill as pickle # Dill exhibits similar behavior
import pickle
class B:
def __init__(self):
self.links = set()
class A:
def __init__(self, base: B):
self.base = base
base.links.add(self)
def __hash__(self):
return hash(self.base)
def __eq__(self, other):
return self.base == other.base
pickled = pickle.dumps(A(B())) # Success
print(pickle.loads(pickled)) # Not so much
上述示例失败,出现以下异常:
Traceback (most recent call last):
File "./mwe.py", line 26, in <module>
print(pickle.loads(pickled))
File "./mwe.py", line 18, in __hash__
return hash(self.base)
AttributeError: 'A' object has no attribute 'base'
正如我所理解的那样,pickle在反序列化B.links
之前尝试反序列化A
。 set
中使用的B
实例尝试在某个时刻调用A.__hash__
,并且由于A
的实例尚未完全构造,因此无法计算自己的哈希值,大家都伤心。
如何在不破坏循环引用的情况下解决这个问题? (打破周期将是很多工作,因为我尝试序列化的对象非常复杂)
答案 0 :(得分:3)
我认为你已经正确地找出了问题的原因。两个实例都依赖于另一个实例,pickle
无法按正确的顺序初始化它们。这可能被认为是一个错误,但幸运的是,这是一个简单的解决方法。
Pickle允许我们使用__getstate__
和__setstate__
功能自定义对象的腌制方式。我们可以使用它在散列之前手动设置base
实例的缺失A
属性:
class B:
def __init__(self):
self.links = set()
def __getstate__(self):
# dump a tuple instead of a set so that the __hash__ function won't be called
return tuple(self.links)
def __setstate__(self, state):
self.links= set()
for link in state:
link.base= self # set the missing attribute
self.links.add(link) # now it can be hashed