为什么python的pickle没有将方法序列化为默认参数?

时间:2016-09-14 10:55:40

标签: python dictionary lambda pickle

我正在尝试使用pickle通过2台服务器之间的线路传输python对象。我创建了一个简单的类,即子类dict,我正在尝试使用pickle进行编组:

def value_is_not_none(value):
    return value is not None

class CustomDict(dict):
    def __init__(self, cond=lambda x: x is not None):
        super().__init__()
        self.cond = cond

    def __setitem__(self, key, value):
        if self.cond(value):
            dict.__setitem__(self, key, value)

我首先尝试使用pickle进行编组,但是当我取消编组时,我收到了与lambda表达式相关的错误。

然后我尝试用dill进行编组,但似乎没有调用__init__

然后我再次尝试使用pickle,但我将value_is_not_none()函数作为cond参数传递 - 再次__init__()似乎没有被调用而且__setitem__()上的编组失败(condNone)。

为什么?我在这里错过了什么?

如果我尝试运行以下代码:

obj = CustomDict(cond=value_is_not_none)
obj['hello'] = ['world']

payload = pickle.dumps(obj, protocol=pickle.HIGHEST_PROTOCOL)
obj2 = pickle.loads(payload)

它以

失败
AttributeError: 'CustomDict' object has no attribute 'cond'

这是一个不同的问题:Python, cPickle, pickling lambda functions 当我尝试将dilllambda一起使用时,它无法正常工作,我也尝试传递一个函数,但它也失败了。

1 个答案:

答案 0 :(得分:2)

pickle正在之前加载您的词典数据它已恢复您实例上的属性。因此,当为字典键值对调用self.cond时,尚未设置__setitem__属性。

请注意,pickle永远不会调用__init__;相反,它会创建一个完全空白实例并直接恢复__dict__属性命名空间。

您有两种选择:

  • 默认为cond=None并忽略条件,如果它仍设置为None

    class CustomDict(dict):
        def __init__(self, cond=None):
            super().__init__()
            self.cond = cond
    
        def __setitem__(self, key, value):
            if getattr(self, 'cond', None) is None or self.cond(value):
                dict.__setitem__(self, key, value)
    

    需要getattr(),因为空白实例根本没有cond属性(它未设置为None,该属性完全丢失)。您可以在课程中添加cond = None

    class CustomDict(dict):
        cond = None
    

    然后只测试if self.cond is None or self.cond(value):

  • 定义自定义__reduce__ method以控制恢复时初始对象的创建方式:

    def _default_cond(v): return v is not None
    
    class CustomDict(dict):
        def __init__(self, cond=_default_cond):
            super().__init__()
            self.cond = cond
    
        def __setitem__(self, key, value):
            if self.cond(value):
                dict.__setitem__(self, key, value)
    
        def __reduce__(self):
            return (CustomDict, (self.cond,), None, None, iter(self.items()))
    
    预计

    __reduce__会返回一个元组:

    • 可以直接腌制的可调用(这里的课很好)
    • 可调用的位置参数元组;在unpickling第一个元素被称为传递第二个元素作为参数,因此通过将其设置为(self.cond,),我们确保创建新实例,其中cond作为参数传入,现在 CustomDict.__init__() 将被调用。
    • 接下来的2个位置适用于__setstate__方法(此处忽略)和类似列表的类型,因此我们将其设置为None
    • 最后一个元素是一个键值对的迭代器,pickle然后将为我们恢复。

    请注意,我在此更改了cond的默认值,因此您不必依赖dill进行酸洗。