如果 __hash__ 被覆盖,Pickle/dill 无法处理循环引用
Pickle/dill cannot handle circular references if __hash__ is overridden
考虑以下 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 会在反序列化 A
之前尝试反序列化 B.links
。 B
中使用的 set
实例试图在某个时候调用 A.__hash__
,并且由于 A
的实例尚未完全构建,它无法计算自己的哈希值,使得大家都很难过
如何在不破坏循环引用的情况下解决这个问题? (打破循环将需要大量工作,因为我要序列化的对象非常复杂)
我认为您已经正确地确定了问题的原因。这两个实例都相互依赖,pickle
无法以正确的顺序初始化它们。这可能被认为是一个错误,但幸运的是有一个简单的解决方法。
Pickle 允许我们使用 __getstate__
and __setstate__
函数自定义对象的腌制方式。我们可以使用它在散列之前手动设置 A
实例缺少的 base
属性:
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
考虑以下 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 会在反序列化 A
之前尝试反序列化 B.links
。 B
中使用的 set
实例试图在某个时候调用 A.__hash__
,并且由于 A
的实例尚未完全构建,它无法计算自己的哈希值,使得大家都很难过
如何在不破坏循环引用的情况下解决这个问题? (打破循环将需要大量工作,因为我要序列化的对象非常复杂)
我认为您已经正确地确定了问题的原因。这两个实例都相互依赖,pickle
无法以正确的顺序初始化它们。这可能被认为是一个错误,但幸运的是有一个简单的解决方法。
Pickle 允许我们使用 __getstate__
and __setstate__
函数自定义对象的腌制方式。我们可以使用它在散列之前手动设置 A
实例缺少的 base
属性:
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