我有一个看起来像这样的默认字典:
dict1 = defaultdict(lambda: defaultdict(int))
问题是,我不能用 cPickle 腌制它。我在这里找到的解决方案之一是使用模块级函数而不是 lambda。我的问题是,什么是模块级功能?如何将字典与 cPickle 一起使用?
我有一个看起来像这样的默认字典:
dict1 = defaultdict(lambda: defaultdict(int))
问题是,我不能用 cPickle 腌制它。我在这里找到的解决方案之一是使用模块级函数而不是 lambda。我的问题是,什么是模块级功能?如何将字典与 cPickle 一起使用?
除了Martijn的解释:
模块级函数是在模块级定义的函数,这意味着它不是一个类的实例方法,它没有嵌套在另一个函数中,它是一个有名字的“真实”函数,而不是 lambda 函数.
因此,要腌制您的defaultdict
,请使用模块级函数而不是 lambda 函数创建它:
def dd():
return defaultdict(int)
dict1 = defaultdict(dd) # dd is a module-level function
你不能腌制它
tmp = pickle.dumps(dict1) # no exception
new = pickle.loads(tmp)
Pickle 想要存储所有实例属性,并且defaultdict
实例存储对default
可调用对象的引用。Pickle 在每个实例属性上递归。
Pickle 无法处理 lambda;pickle 只处理数据,而不是代码,并且 lambdas 包含代码。函数可以被腌制,但就像类定义一样,只有当函数可以被导入时。可以导入在模块级别定义的函数。在这种情况下,Pickle 只存储一个字符串,即在再次 unpickle 时要导入和引用的函数的完整“路径”。
但是,您可以使用partial
来完成此操作:
>>> from collections import defaultdict
>>> from functools import partial
>>> pickle.loads(pickle.dumps(defaultdict(partial(defaultdict, int))))
defaultdict(<functools.partial object at 0x94dd16c>, {})
为此,只需编写您想要编写的代码。我会使用dill,它可以序列化 lambdas 和 defaultdicts。Dill 可以在 python 中序列化几乎任何东西。
>>> import dill
>>> from collections import defaultdict
>>>
>>> dict1 = defaultdict(lambda: defaultdict(int))
>>> pdict1 = dill.dumps(dict1)
>>> _dict1 = dill.loads(pdict1)
>>> _dict1
defaultdict(<function <lambda> at 0x10b31b398>, {})
在这种情况下仍然可以作为单行的解决方案,并且实际上比启动的lambda
(或等效的def
-ed)函数更有效:
dict1 = defaultdict(defaultdict(int).copy)
这只是创建一个模板defaultdict(int)
,并将其方法绑定copy
为外部的默认工厂defaultdict
。那里的所有东西都是可挑选的,并且在 CPython(其中defaultdict
是用 C 实现的内置类型)上,它比调用任何用户定义的函数来完成相同的工作更有效。无需额外的导入、包装等。
dict1 = defaultdict(lambda: defaultdict(int))
cPickle.dump(dict(dict1), file_handle)
为我工作
通过普通函数实现匿名 lambda 函数对我有用。正如 Mike 所指出的,Pickle 无法处理 lambda。pickle 只处理数据。因此,将 defaultdict 方法从:
dict_ = defaultdict(lambda: default_value)
至:
def default_():
return default_value
然后按如下方式创建默认字典对我有用:
dict_ = defaultdict(default_)
如果您不关心保留 defaultdict 类型,请将其转换:
fname = "file.pkl"
for value in nested_default_dict:
nested_default_dict[value] = dict(nested_default_dict[value])
my_dict = dict(nested_default_dict)
with open(fname, "wb") as f:
pickle.dump(my_dict, f) # Now this will work
我认为这是一个很好的选择,因为当你腌制时,对象可能是它的最终形式......而且,如果真的再次需要 defaultdict 类型,你可以简单地在你解开之后转换回来:
for value in my_dict:
my_dict[value] = defaultdict(type, my_dict[value])
nested_default_dict = defaultdict(type, my_dict)
我目前正在做类似于问题提出者的事情,但是,我正在使用 defaultdict 的一个子类,它有一个用作 default_factory 的成员函数。为了让我的代码正常工作(我需要在运行时定义函数),我只是添加了一些代码来准备对象进行酸洗。
代替:
...
pickle.dump(dict, file)
...
我用这个:
....
factory = dict.default_factory
dict.default_factory = None
pickle.dump(dict, file)
dict.default_factory = factory
...
这不是我使用的确切代码,因为我的树是一个对象,它创建与请求索引相同的树类型的实例(因此我使用递归成员函数来执行 pre/post 泡菜操作),但这种模式也回答问题。
这是用于任意嵌套深度的任意基本默认字典的函数。
def wrap_defaultdict(instance, times):
"""Wrap an instance an arbitrary number of `times` to create nested defaultdict.
Parameters
----------
instance - e.g., list, dict, int, collections.Counter
times - the number of nested keys above `instance`; if `times=3` dd[one][two][three] = instance
Notes
-----
using `x.copy` allows pickling (loading to ipyparallel cluster or pkldump)
- thanks https://stackoverflow.com/questions/16439301/cant-pickle-defaultdict
"""
from collections import defaultdict
def _dd(x):
return defaultdict(x.copy)
dd = defaultdict(instance)
for i in range(times-1):
dd = _dd(dd)
return dd