Python: невозможно сортировать тип X, сбой поиска атрибута

Я пытаюсь разжечь namedtuple:

from collections import namedtuple
import cPickle

class Foo:

    Bar = namedtuple('Bar', ['x', 'y'])

    def baz(self):
        s = set()
        s.add(Foo.Bar(x=2, y=3))
        print cPickle.dumps(s)

if __name__ == '__main__':
    f = Foo()
    f.baz()

Это приводит к следующему выводу:

Traceback (most recent call last):
  File "scratch.py", line 15, in <module>
    f.baz()
  File "scratch.py", line 11, in baz
    print cPickle.dumps(s)
cPickle.PicklingError: Can't pickle <class '__main__.Bar'>: attribute lookup __main__.Bar failed

Что я делаю неправильно? Проблема в том, что Bar является членом Foo? (Перемещение определения Bar на верхний уровень решает проблему, хотя мне все еще интересно, почему это происходит.)

Ответ 1

Да, тот факт, что это член класса является проблемой:

>>> class Foo():
...     Bar = namedtuple('Bar', ['x','y'])
...     def baz(self):
...         b = Foo.Bar(x=2, y=3)
...         print(type(b))
...
>>> a = Foo()
>>> a.baz()
<class '__main__.Bar'>

Проблема заключается в том, что когда namedtuple() возвращает объект типа, он не знает о том, что ему присваивается член класса, и, следовательно, он сообщает, что тип объекта, который он набирает, должен быть __main__.Bar, хотя это действительно должно быть __main__.Foo.Bar.

Ответ 2

Вложенные классы приводят к откату pickle, так как он опирается на путь объекта внутри вашего приложения, чтобы впоследствии его восстановить.

Непосредственным решением является не встраивание классов, т.е. перемещение Bar на внешний Foo. Код будет работать все равно.

Но лучше всего не использовать pickle вообще для хранения данных. Используйте другой формат сериализации, например json, или базу данных, например sqlite3.

Вы только что поразили одно из многих неудобств рассола, если вы меняете свой код, перемещаете вещи или иногда делаете небольшие структурные изменения, ваши данные становятся неактивными.

Кроме того, у рассола есть и другие недостатки: он медленный, небезопасный, только python...

Ответ 3

Использование укропа вместо рассола здесь позволит это работать