pickle 使用 namedtuple 时错用命名导致 PicklingError

文章讨论了在使用pickle存储自定义namedtuple实例时遇到的问题,指出只有定义在模块顶层的类才能被正确序列化。解决方案包括在顶层定义namedtuple类型或使用默认名称如P而非用户自定义名称。
摘要由CSDN通过智能技术生成

当使用 pickle 存储 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()

然后,我更改了两行代码,使用我自己创建的 named tuple:

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 参数需要与工厂的名称匹配才能正常工作?

2、解决方案

Python 文档的《什么可以被腌制和反腌制?》部分说明,只有“定义在模块顶层的类”才能被腌制。然而,namedtuple() 是一个工厂函数,它实际上是定义了一个类(在你的第二个示例中是 my_typename(tuple)),但它并没有将生成的类型分配给模块顶层的 my_typename 变量。

这是因为 pickle 只会保存这些对象的“完全限定名称”,而不是它们的代码,为了能够在之后进行反腌制,必须能够使用此名称从它们所在的模块导入它们(因此要求模块在顶层包含具有该名称的命名对象)。

可以通过查看一个解决方案来说明这个问题——更改一行代码,以便在顶层定义名为 my_typename 的类型:

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

或者,你可以直接将 namedtuple 命名为“P”,而不是“my_typename”:

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

至于你看到的 namedtuple.py 源代码的作用:它试图确定调用者(创建 namedtuple 的对象)所在的模块名称,因为作者知道 pickle 可能会使用它来导入定义以进行反腌制,而且人们通常将结果分配给与他们传递给工厂函数的名称相同的变量(但你在第二个示例中没有这样做)。

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值