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

why python's pickle is not serializing a method as default argument?

我正在尝试使用 pickle 在 2 个服务器之间通过网络传输 python 对象。我创建了一个简单的 class,subclasses 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 一起使用时,它无法正常工作,我还尝试传递一个函数,但它也失败了。

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

注意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:

    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__ 预计 return 一个元组:

    • 可以直接 pickle 的可调用函数(这里 class 可以)
    • 该可调用对象的位置参数元组;在 unpickling 上调用第一个元素作为参数传入第二个元素,因此通过将其设置为 (self.cond,) 我们确保创建新实例时使用 cond 作为参数传入并且 now CustomDict.__init__() 调用。
    • 接下来的 2 个位置用于 __setstate__ 方法(此处忽略)和类列表类型,因此我们将它们设置为 None
    • 最后一个元素是 pickle 将为我们恢复的键值对的迭代器。

    请注意,我在这里也用一个函数替换了 cond 的默认值,因此您不必依赖 dill 进行酸洗。