了解Python中的namedtuple typename和pickle的问题

时间:2013-11-22 11:24:00

标签: python python-2.7 serialization pickle namedtuple

今天早些时候,我在尝试挑选namedtuple个实例时遇到了麻烦。作为一个完整性检查,我尝试运行一些已发布的代码in another answer。在这里,简化了一点:

from collections import namedtuple
import pickle

P = namedtuple("P", "one two three four")

def pickle_test():
    abe = P("abraham", "lincoln", "vampire", "hunter")
    f = open('abe.pickle', 'w')
    pickle.dump(abe, f)
    f.close()

pickle_test()

然后我更改了两行来使用我的命名元组:

from collections import namedtuple
import pickle

P = namedtuple("my_typename", "A B C")

def pickle_test():
    abe = P("ONE", "TWO", "THREE")
    f = open('abe.pickle', 'w')
    pickle.dump(abe, f)
    f.close()

pickle_test()

然而这给了我错误

  File "/path/to/anaconda/lib/python2.7/pickle.py", line 748, in save_global
    (obj, module, name))
pickle.PicklingError: Can't pickle <class '__main__.my_typename'>: it's not found as __main__.my_typename

即。 Pickle模块正在寻找my_typename。我将第P = namedtuple("my_typename", "A B C")行更改为P = namedtuple("P", "A B C")并且有效。

我查看了namedtuple.py的来源,最后我们看到了相似的内容,但我并不完全了解发生了什么:

# For pickling to work, the __module__ variable needs to be set to the frame
# where the named tuple is created.  Bypass this step in enviroments where
# sys._getframe is not defined (Jython for example) or sys._getframe is not
# defined for arguments greater than 0 (IronPython).
try:
    result.__module__ = _sys._getframe(1).f_globals.get('__name__', '__main__')
except (AttributeError, ValueError):
    pass

return result

所以我的问题是到底发生了什么?为什么typename参数需要与工厂名称匹配才能生效?

1 个答案:

答案 0 :(得分:8)

在Python文档的标题为What can be pickled and unpickled?的部分中,它表示只能“挑选”在模块顶层定义的类“。但是namedtuple()是一个工厂函数,它有效地定义了一个类(在第二个示例中为my_typename(tuple)),但它并没有将制造的类型分配给名为{{1}的变量}在模块的顶层。

这是因为my_typename仅保存此类内容的“完全限定”名称,而不保存其代码,并且它们必须pickle能够使用此名称从他们所使用的模块中获取以后可以进行unpickled(因此要求模块必须在顶层包含命名对象)。

这可以通过查看问题的一种解决方法来说明 - 这将是更改代码的一行,以便在顶级定义名为import 的类型:< / p>

my_typename

或者,您可以将P = my_typename = namedtuple("my_typename", "A B C") 命名为namedtuple而不是"P"

"my_typename"

至于您正在查看的P = namedtuple("P", "A B C") 源代码的作用:它正在尝试确定调用者(namedtuple.py的创建者)所在的模块名称,因为作者知道{{ 1}}可能会尝试使用它来namedtuple定义进行unpickling,并且人们通常将结果分配给变量,并使用与传递给工厂函数相同的名称(但在第二个示例中没有)