我正在学习如何使用pickle。我创建了一个命名元组对象,将其附加到一个列表中,并尝试腌制该列表。但是,我收到以下错误:
pickle.PicklingError: Can't pickle <class '__main__.P'>: it's not found as __main__.P
我发现如果我运行代码而不将其包装在函数中,它会完美运行。包装在函数内时是否需要额外的步骤来腌制对象?
这是我的代码:
from collections import namedtuple
import pickle
def pickle_test():
P = namedtuple("P", "one two three four")
my_list = []
abe = P("abraham", "lincoln", "vampire", "hunter")
my_list.append(abe)
with open('abe.pickle', 'wb') as f:
pickle.dump(abe, f)
pickle_test()
创建函数的命名元组outside:
from collections import namedtuple
import pickle
P = namedtuple("P", "one two three four")
def pickle_test():
my_list = []
abe = P("abraham", "lincoln", "vampire", "hunter")
my_list.append(abe)
with open('abe.pickle', 'wb') as f:
pickle.dump(abe, f)
pickle_test()
现在
pickle
可以找到它;它现在是一个全局模块。当 unpickle 时,pickle
模块所要做的就是再次定位 __main__.P
。在您的版本中, P
是 pickle_test()
函数的 local,并且不可内省或不可导入。
请注意,
pickle
仅存储模块和类名称,取自类的__name__
属性。确保 namedtuple()
调用的第一个参数与您要分配给的全局变量匹配; P.__name__
一定是"P"
!
重要的是要记住,
namedtuple()
是一个类工厂;你给它参数,它返回一个类对象供你创建实例。 pickle
仅存储实例中包含的data,加上对原始类的字符串引用以再次重建实例。
我在另一个线程中找到了这个答案。
为了让 pickling 正常工作,分配给namedtuple的变量必须与namedtuple本身具有相同的名称
group_t = namedtuple('group_t', 'field1, field2') # this will work
mismatched_group_t = namedtuple('group_t', 'field1, field2') # this will throw the error
将我的问题添加为主要答案的评论后,我找到了一种方法来解决动态创建的
namedtuple
可腌制的问题。在我的情况下这是必需的,因为我仅在运行时(在数据库查询之后)计算出其字段。
我所做的就是 monkey patch
namedtuple
,有效地将其移动到 __main__
模块:
def _CreateNamedOnMain(*args):
import __main__
namedtupleClass = collections.namedtuple(*args)
setattr(__main__, namedtupleClass.__name__, namedtupleClass)
namedtupleClass.__module__ = "__main__"
return namedtupleClass
请注意,如果您不小心,
namedtuple
名称(由args
提供)可能会覆盖__main__
中的另一个成员。
cloudpickle
或 dill
进行序列化:
from collections import namedtuple
import cloudpickle
import dill
def dill_test(dynamic_names):
P = namedtuple('P', dynamic_names)
my_list = []
abe = P("abraham", "lincoln", "vampire", "hunter")
my_list.append(abe)
with open('deleteme.cloudpickle', 'wb') as f:
cloudpickle.dump(abe, f)
with open('deleteme.dill', 'wb') as f:
dill.dump(abe, f)
dill_test("one two three four")
这里的问题是子进程无法导入对象的类 - 在本例中为类 P -,在多模型项目的情况下,类 P 应该可以在使用子进程的任何地方导入
一个快速的解决方法是通过影响 globals() 来使其可导入
globals()["P"] = P