我有一个类似这样的defaultdict:
dict1 = defaultdict(lambda: defaultdict(int))
问题是,我无法使用cPickle对其进行pickle。我在这里发现的解决方案之一是使用模块级函数而不是lambda表达式。我的问题是,什么是模块级函数?我如何使用字典和cPickle?
我有一个类似这样的defaultdict:
dict1 = defaultdict(lambda: defaultdict(int))
问题是,我无法使用cPickle对其进行pickle。我在这里发现的解决方案之一是使用模块级函数而不是lambda表达式。我的问题是,什么是模块级函数?我如何使用字典和cPickle?
除了 Martijn的解释 之外:
模块级函数是在模块级别定义的函数,这意味着它不是类的实例方法,也不是嵌套在另一个函数中的函数,而是一个有名称的“真实”函数,而不是一个 lambda 函数。
因此,为了将您的 defaultdict
进行 pickle 化,请使用模块级函数创建它,而不是使用 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只能处理数据,而不是代码,而lambda函数包含代码。函数可以被pickled,但是像类定义一样,只有在函数可以被导入时才能pickled。在模块级别定义的函数可以被导入。在这种情况下,Pickle仅存储一个字符串,即要导入和在反pickling时引用的函数的完整'路径'。
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)
。该代码演示了它可以成功地被pickle。 - jamylak为了实现这一点,只需编写您想要编写的代码。我建议使用dill,它可以序列化lambda和defaultdict。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
中。 - Mike McKernslambda
函数(或等效的def
函数)更高效。dict1 = defaultdict(defaultdict(int).copy)
这只是创建了一个模板defaultdict(int)
,并将其copy
方法绑定为外部defaultdict
的默认工厂。里面的所有内容都是可选的,并且在CPython(其中defaultdict
是用C实现的内置类型)中,它比调用任何用户定义的函数更有效地完成相同的工作。不需要额外的导入、包装等操作。
lambda
的代码路径,defaultdict(int).copy
的代码路径可以进一步优化并且应该能够击败lambda
如果这样做)。虽然它仍然是一种pickle
友好的单行代码。 - ShadowRangerdict1 = defaultdict(lambda: defaultdict(int))
cPickle.dump(dict(dict1), file_handle)
通过普通函数实现匿名 lambda 函数对我很有帮助。正如 Mike 指出的那样,Pickle 无法处理 lambda 表达式;它只能处理数据。因此,将 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)
...
pickle.dump(dict, file)
...
....
factory = dict.default_factory
dict.default_factory = None
pickle.dump(dict, file)
dict.default_factory = factory
...
这不是我使用的确切代码,因为我的树是一个对象,它创建了相同类型的树的实例,当索引被请求时(所以我使用递归成员函数来执行预处理/后处理),但这种模式也回答了这个问题。
default_factory
,那么这只是一个好方法。如果您不再需要工厂,则可以将其设置为 None
并完成操作 (: - drevickodef 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://dev59.com/sWQn5IYBdhLWcg3w9K_3
"""
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
def dd(): return 'something'
mydict = defaultdict(dd)
起作用了。 - joshi123defaultdict
两次有点令人困惑。 - joshi123