【问题标题】:Can't pickle defaultdict不能腌制默认字典
【发布时间】:2013-05-02 14:27:23
【问题描述】:

我有一个如下所示的默认字典:

dict1 = defaultdict(lambda: defaultdict(int))

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

【问题讨论】:

    标签: python pickle defaultdict


    【解决方案1】:

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

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

    【讨论】:

      【解决方案2】:

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

      >>> from collections import defaultdict
      >>> from functools import partial
      >>> pickle.loads(pickle.dumps(defaultdict(partial(defaultdict, int))))
      defaultdict(<functools.partial object at 0x94dd16c>, {})
      

      【讨论】:

      • @Fred 基本上只是一个defaultdict 默认值。是defaultdict(int)。代码证明它可以成功腌制
      【解决方案3】:

      除了Martijn's explanation

      模块级函数是在模块级定义的函数,这意味着它不是一个类的实例方法,它不嵌套在另一个函数中,它是一个有名字的“真实”函数,而不是一个 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) 有效
      • 你是对的@joshi123。为了让它工作,有必要用我们真正的默认值替换 retunr defaultdict(int) 。就我而言,我正在返回一个字符串文字。然后'返回'n''。谢谢!
      • 刚刚意识到@Addishiwot Shimels 已经在下面给出了这个答案
      • @joshi123 那么您遇到了哪个错误?代码运行良好:ideone.com/ZoNKnh。此外,我看不出 Addishiwot Shimels 的答案与我的答案有何不同,因为它的作用相同:用模块级函数替换 lambda。
      • @sloth 现在似乎无法重现它,抱歉我的错误。我认为两次调用defaultdict 有点令人困惑
      【解决方案4】:

      我目前正在做类似于问题提出者的事情,但是,我正在使用 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 即可完成(:
      【解决方案5】:

      为此,只需编写您想要编写的代码。我会使用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>, {})
      

      【讨论】:

      • 这很好用。有没有办法将 dict1 转储到临时文件中,然后再次加载?类似于文件写入和读取的泡菜操作..
      • 当然。 dill 提供常用的dumpload,可以像pickle 中的dumpload 一样使用。此外,您可能想查看dill.temp.dump 转储到NamedTemporaryFile
      • 谢谢,查看我个人资料上的最新问题。你可以在那里发布你的答案。 :)
      【解决方案6】:

      如果你不关心保留 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)
      

      【讨论】:

        【解决方案7】:
        dict1 = defaultdict(lambda: defaultdict(int))
        cPickle.dump(dict(dict1), file_handle)
        

        为我工作

        【讨论】:

        • 对我来说。谢谢
        【解决方案8】:

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

            dict_ = defaultdict(lambda: default_value)
        

        到:

            def default_():
                return default_value
        

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

            dict_ = defaultdict(default_)
        

        【讨论】:

        • 我看不出这对sloth's answer from six years before 有什么好处...
        • @ShadowRanger 考虑到多年来语言的发展,知道六年前曾经存在的东西今天仍然存在是有一些价值的。也许这可以在答案中明确说明。
        【解决方案9】:

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

        dict1 = defaultdict(defaultdict(int).copy)
        

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

        【讨论】:

        • 优雅的解决方案
        • 好主意!
        • @OliverBaumann:谢谢!碰巧的是,关于性能的评论不再适用(参见update to answer and comments here),尽管这可能是一个临时问题(他们优化了影响lambdas 的代码路径;defaultdict(int).copy 的代码路径可以进一步优化,应该是如果这样做了,就可以击败lambda)。不过,作为pickle 友好的单线,它仍然很好。
        【解决方案10】:

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

        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
        

        【讨论】:

          猜你喜欢
          • 2018-06-15
          • 2020-03-03
          • 2011-06-29
          • 1970-01-01
          • 2011-05-04
          • 1970-01-01
          • 1970-01-01
          • 2018-08-18
          • 2011-12-14
          相关资源
          最近更新 更多