【发布时间】:2013-05-02 14:27:23
【问题描述】:
我有一个如下所示的默认字典:
dict1 = defaultdict(lambda: defaultdict(int))
问题是,我不能用 cPickle 腌制它。我在这里找到的解决方案之一是使用模块级函数而不是 lambda。我的问题是,什么是模块级功能?如何将字典与 cPickle 一起使用?
【问题讨论】:
标签: python pickle defaultdict
我有一个如下所示的默认字典:
dict1 = defaultdict(lambda: defaultdict(int))
问题是,我不能用 cPickle 腌制它。我在这里找到的解决方案之一是使用模块级函数而不是 lambda。我的问题是,什么是模块级功能?如何将字典与 cPickle 一起使用?
【问题讨论】:
标签: python pickle defaultdict
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>, {})
【讨论】:
defaultdict 默认值。是defaultdict(int)。代码证明它可以成功腌制
模块级函数是在模块级定义的函数,这意味着它不是一个类的实例方法,它不嵌套在另一个函数中,它是一个有名字的“真实”函数,而不是一个 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)
【讨论】:
def dd(): return 'something' mydict = defaultdict(dd) 有效
defaultdict 有点令人困惑
我目前正在做类似于问题提出者的事情,但是,我正在使用 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 泡菜操作),但是这个模式也回答了这个问题。
【讨论】:
default_factory 时才有用。如果您不再需要工厂,只需将其设置为None 即可完成(:
为此,只需编写您想要编写的代码。我会使用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>, {})
【讨论】:
dill 提供常用的dump 和load,可以像pickle 中的dump 和load 一样使用。此外,您可能想查看dill.temp.dump 转储到NamedTemporaryFile。
如果你不关心保留 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 类型,你可以简单地在你 unpickle 后转换回来:
for value in my_dict:
my_dict[value] = defaultdict(type, my_dict[value])
nested_default_dict = defaultdict(type, my_dict)
【讨论】:
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_)
【讨论】:
在这种情况下仍然可以作为单行的解决方案,并且实际上比lambda(或等效的def-ed)启动功能更有效:
dict1 = defaultdict(defaultdict(int).copy)
这只是创建了一个模板defaultdict(int),并将其copy 方法绑定为外部defaultdict 的默认工厂。里面的所有东西都是可挑选的,并且在 CPython(其中defaultdict 是用 C 实现的内置类型)上,它比调用任何用户定义的函数来完成相同的工作更有效。无需额外的导入、包装等。
【讨论】:
lambdas 的代码路径;defaultdict(int).copy 的代码路径可以进一步优化,应该是如果这样做了,就可以击败lambda)。不过,作为pickle 友好的单线,它仍然很好。
这是一个用于任意基本默认字典的函数,用于任意深度的嵌套。
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
【讨论】: