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

YXD*_*YXD 6 python serialization pickle namedtuple python-2.7

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

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()
Run Code Online (Sandbox Code Playgroud)

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

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()
Run Code Online (Sandbox Code Playgroud)

然而,这给了我错误

  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
Run Code Online (Sandbox Code Playgroud)

即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
Run Code Online (Sandbox Code Playgroud)

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

mar*_*eau 8

在标题为什么可以腌制和未腌制?在Python文档中,它表明只能"挑选"在模块顶层定义的类".但是namedtuple(),工厂函数有效地定义了一个类(my_typename(tuple)在第二个示例中),但是它没有将制造类型分配给my_typename在模块顶层命名的变量.

这是因为pickle只保存了这些东西的"完全限定"名称,而不是它们的代码,并且它们必须import能够从他们使用此名称的模块中获得,以便以后能够进行打开(因此要求模块必须包含顶级的命名对象).

这可以通过观察一个解决方法为问题-这是改变使得所述类型的代码中的一个行中所示my_typename 在顶部级别定义:

P = my_typename = namedtuple("my_typename", "A B C")
Run Code Online (Sandbox Code Playgroud)

或者,您可以只提供namedtuple名称"P"而不是"my_typename":

P = namedtuple("P", "A B C")
Run Code Online (Sandbox Code Playgroud)

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