Рассол / укроп не может обрабатывать циклические ссылки, если __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
Другие вопросы по тегам