今天早些时候,我在尝试挑选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
参数需要与工厂名称匹配才能生效?
答案 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,并且人们通常将结果分配给变量,并使用与传递给工厂函数相同的名称(但在第二个示例中没有)