如果重写__hash__,则Pickle / dill无法处理循环引用

时间:2017-07-03 13:54:25

标签: python serialization pickle circular-reference dill

考虑以下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之前尝试反序列化Aset中使用的B实例尝试在某个时刻调用A.__hash__,并且由于A的实例尚未完全构造,因此无法计算自己的哈希值,大家都伤心。

如何在不破坏循环引用的情况下解决这个问题? (打破周期将是很多工作,因为我尝试序列化的对象非常复杂)

1 个答案:

答案 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