Как распиливать и распаковывать экземпляры класса, который наследуется от defaultdict?

У меня есть класс, который наследует от defaultdict следующим образом:

class listdict(defaultdict):
    def __init__(self):
        defaultdict.__init__(self, list)

Я могу рассолить его, но когда я его распакую, это происходит:

('__init__() takes exactly 1 argument (2 given)', <class 'listdict'>, (<type 'list'>,))

Класс не определяет никаких специальных методов протокола pickle. Травление и раскалывание нормального defaultdict(list) работает, как ожидалось. Может ли кто-нибудь просветить меня?

Ответ 1

Типы определяют, как экземпляры его получают маринованным путем определения одного или нескольких (довольно больших) наборов методов. У каждого есть свое тонкое поведение. См. документы в протоколе рассола. В случае collections.defaultdict он использует метод __reduce__:

>>> l = collections.defaultdict(list)
>>> l.__reduce__()
(<type 'collections.defaultdict'>, (<type 'list'>,), None, None, <dictionary-itemiterator object at 0x7f031fb3c470>)

Первым элементом кортежа является тип, а второй элемент - кортеж аргументов, передаваемый типу при его создании. Если вы не переопределите __reduce__, первый элемент будет правильно изменен на ваш тип, но второй элемент не будет. Это вызывает ошибку, которую вы видите. Яркий пример того, как вы могли это исправить:

>>> import collections
>>> import pickle
>>> class C(collections.defaultdict):
...     def __init__(self):
...         collections.defaultdict.__init__(self, list)
...     def __reduce__(self):
...         t = collections.defaultdict.__reduce__(self)
...         return (t[0], ()) + t[2:]
...
>>> c = C()
>>> c[1].append(2)
>>> c[2].append(3)
>>> c2 = pickle.loads(pickle.dumps(c))
>>> c2 == c
True

Это только грубый пример, потому что там больше травления (например, __reduce_ex__), и все это довольно сложно. В этом случае использование __getinitargs__ может быть более удобным.

В качестве альтернативы вы можете сделать способ класса __init__ взять необязательный вызываемый, по умолчанию - list, или вы можете просто использовать функцию вместо класса:

def listdict():
    return collections.defaultdict(list)

Ответ 2

Эта ошибка указывает, что ваш класс listdict должен был принимать один аргумент (неявное я), но получил два аргумента.

Ваш класс наследует от defaultdict и определяет инициализатор. Этот инициализатор вызывает инициализатор defaultdict и передает ему "список", который в этом случае может быть либо функцией, либо классом. (Я не могу потрудиться, чтобы проверить).

То, что вы, вероятно, имели в виду, это сделать:

class listdict(defaultdict):
    def __init__(self, list):
        defaultdict.__init__(self, list)

Теперь, когда listdict инициализируется данным списком, он передает THAT-список конструктору defaultdict, а не передает ссылку на глобальный список.

(Тем не менее, он считал, что плохой стиль использует имя, такое же, как общие глобальные методы и классы, такие как "str", "list" и т.д. по той же причине, что вы запутались).