我正在尝试使用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__()
上的编组失败(cond
为None
)。
为什么?我在这里错过了什么?
如果我尝试运行以下代码:
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
当我尝试将dill
与lambda
一起使用时,它无法正常工作,我也尝试传递一个函数,但它也失败了。
答案 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__
会返回一个元组:
(self.cond,)
,我们确保创建新实例,其中cond
作为参数传入,现在 CustomDict.__init__()
将被调用。__setstate__
方法(此处忽略)和类似列表的类型,因此我们将其设置为None
。
请注意,我在此更改了cond
的默认值,因此您不必依赖dill
进行酸洗。