我正在尝试腌制一个
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
的定义移至顶层可以解决问题,尽管我仍然很好奇为什么会发生这种情况。)
是的,作为班级成员这一事实是一个问题:
>>> 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
。
嵌套类会使 pickle 失败,因为它依赖于应用程序内对象的路径来稍后重建它。
直接的解决方案是不要嵌套类,即将
Bar
定义移到 Foo
之外。代码仍然可以工作。
但更好的做法是根本不使用
pickle
来存储数据。使用其他序列化格式,例如 json
,或数据库,例如 sqlite3
。
您刚刚遇到了pickle的众多不便之处之一,如果您更改代码,移动内容,或者有时进行小的结构更改,您的数据将变得无法加载。
除此之外,pickle 还有其他缺点:速度慢、不安全、仅限 python...
用莳萝代替泡菜可以让这个起作用
这里的解决方案是将命名元组定义移动到模块级别,然后 pickle 就可以工作了。这里有详细的解答:
如果您通过搜索引擎来到这里,问题也可能是类型名称和第一个参数不相同。例如:
FooBar = namedtuple('foo_bar', ['x', 'y'])
应该是
FooBar = namedtuple('FooBar', ['x', 'y'])
在 Python 3.5+ 中,您可以使用
typing.NamedTuple
而不是 collections.namedtuple
来修复此问题:
from typing import NamedTuple
class Foo:
class Bar(NamedTuple):
x: int
y: int
if __name__ == '__main__':
import pickle
s = {Foo.Bar(x=2, y=3)}
p = pickle.dumps(s, 4)
assert pickle.loads(p) == s