Рассол / укроп не может обрабатывать циклические ссылки, если __hash__ переопределен
Рассмотрим следующее 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'
Как я понимаю проблему, рассол пытается десериализовать B.links
до десериализации A
, set
экземпляр используется в B
попытки вызвать A.__hash__
в какой-то момент, и с момента A
еще не полностью построен, он не может вычислить свой собственный хэш, что делает всех грустными.
Как мне обойти это, не нарушая циклические ссылки? (Прерывание циклов было бы большой работой, потому что объект, который я пытаюсь сериализовать, смешно сложен)
1 ответ
Я думаю, что вы правильно определили причину проблемы. Оба случая зависят от другого, и 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