Python:无法序列化类型X,属性查找失败

67 投票
6 回答
79352 浏览
提问于 2025-04-16 09:53

我正在尝试把一个 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

我哪里做错了呢?是因为 BarFoo 的一个成员吗?(把 Bar 的定义放到最上面就解决了这个问题,不过我还是想知道为什么会这样。)

6 个回答

10

在这里用dill代替pickle可以让这个功能正常工作。

16

嵌套类会导致pickle无法正常工作,因为pickle需要知道对象在你应用程序中的路径,以便之后能正确地重建它。

解决这个问题的直接方法就是不要嵌套类,也就是说,把Bar的定义放到Foo外面。这样代码依然可以正常运行。

不过,更好的做法是根本不要使用 pickle来存储数据。可以使用其他的序列化格式,比如json,或者使用数据库,比如sqlite3

你刚刚遇到了pickle的一个常见问题,如果你修改代码、调整结构,或者进行一些小的改动,你的数据可能就无法加载了。

除此之外,pickle还有其他缺点:它运行速度慢、安全性差,而且只适用于Python。

41

是的,把它当作类的成员确实会有问题:

>>> 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

撰写回答