58

我有一个看起来像这样的默认字典:

dict1 = defaultdict(lambda: defaultdict(int))

问题是,我不能用 cPickle 腌制它。我在这里找到的解决方案之一是使用模块级函数而不是 lambda。我的问题是,什么是模块级功能?如何将字典与 cPickle 一起使用?

4

10 回答 10

69

除了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)
于 2013-05-08T11:40:01.350 回答
19

Pickle 想要存储所有实例属性,并且defaultdict实例存储对default可调用对象的引用。Pickle 在每个实例属性上递归。

Pickle 无法处理 lambda;pickle 只处理数据,而不是代码,并且 lambdas 包含代码。函数可以被腌制,但就像类定义一样,只有当函数可以被导入时。可以导入在模块级别定义的函数。在这种情况下,Pickle 只存储一个字符串,即在再次 unpickle 时要导入和引用的函数的完整“路径”。

于 2013-05-08T11:30:44.503 回答
13

但是,您可以使用partial来完成此操作:

>>> from collections import defaultdict
>>> from functools import partial
>>> pickle.loads(pickle.dumps(defaultdict(partial(defaultdict, int))))
defaultdict(<functools.partial object at 0x94dd16c>, {})
于 2013-05-08T11:39:15.620 回答
7

为此,只需编写您想要编写的代码。我会使用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>, {})
于 2014-01-20T16:47:17.367 回答
5

在这种情况下仍然可以作为单行的解决方案,并且实际上比启动的lambda(或等效的def-ed)函数更有效:

dict1 = defaultdict(defaultdict(int).copy)

这只是创建一个模板defaultdict(int),并将其方法绑定copy为外部的默认工厂defaultdict。那里的所有东西都是可挑选的,并且在 CPython(其中defaultdict是用 C 实现的内置类型)上,它比调用任何用户定义的函数来完成相同的工作更有效。无需额外的导入、包装等。

于 2020-11-01T03:23:10.900 回答
4
dict1 = defaultdict(lambda: defaultdict(int))
cPickle.dump(dict(dict1), file_handle)

为我工作

于 2018-07-05T05:34:38.997 回答
3

通过普通函数实现匿名 lambda 函数对我有用。正如 Mike 所指出的,Pickle 无法处理 lambda。pickle 只处理数据。因此,将 defaultdict 方法从:

    dict_ = defaultdict(lambda: default_value)

至:

    def default_():
        return default_value

然后按如下方式创建默认字典对我有用:

    dict_ = defaultdict(default_)
于 2019-09-19T08:02:02.267 回答
2

如果您不关心保留 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)
于 2016-07-19T10:48:21.310 回答
1

我目前正在做类似于问题提出者的事情,但是,我正在使用 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 泡菜操作),但这种模式也回答问题。

于 2014-01-03T14:58:01.337 回答
0

这是用于任意嵌套深度的任意基本默认字典的函数。

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
于 2021-07-15T16:19:01.240 回答