如何使 class 具有 __getattr__ 的可正确拾取?
How to make a class which has __getattr__ properly pickable?
我以一种简单的方式扩展了 dict
以使用 d.key
符号而不是 d['key']
:
直接访问它的值
class ddict(dict):
def __getattr__(self, item):
return self[item]
def __setattr__(self, key, value):
self[key] = value
现在当我尝试 pickle 时,它会调用 __getattr__
来查找 __getstate__
,这既不存在也不必要。使用 __setstate__
:
解酸会发生同样的情况
>>> import pickle
>>> class ddict(dict):
... def __getattr__(self, item):
... return self[item]
... def __setattr__(self, key, value):
... self[key] = value
...
>>> pickle.dumps(ddict())
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in __getattr__
KeyError: '__getstate__'
我该如何修改 class ddict
才能正确选取?
问题不在于 pickle
,而是您的 __getattr__
方法引发了 KeyError
异常,从而违反了预期的约定。您需要修复 __getattr__
方法以引发 AttributeError
异常:
def __getattr__(self, item):
try:
return self[item]
except KeyError:
raise AttributeError(item)
现在 pickle
得到了缺失 __getstate__
自定义挂钩的预期信号。
来自object.__getattr__
documentation:
This method should return the (computed) attribute value or raise an AttributeError
exception.
(大胆强调我的)。
如果您坚持保留 KeyError
,那么至少您需要跳过以双下划线开头和结尾的名称,并只为那些 AttributeError
加注:
def __getattr__(self, item):
if isinstance(item, str) and item[:2] == item[-2:] == '__':
# skip non-existing dunder method lookups
raise AttributeError(item)
return self[item]
请注意,您可能想给您的 ddict()
subclass an empty __slots__
tuple;您的实例不需要额外的 __dict__
属性映射,因为您正在将属性转移到 key-value 对。这样可以为每个实例节省大量内存。
演示:
>>> import pickle
>>> class ddict(dict):
... __slots__ = ()
... def __getattr__(self, item):
... try:
... return self[item]
... except KeyError:
... raise AttributeError(item)
... def __setattr__(self, key, value):
... self[key] = value
...
>>> pickle.dumps(ddict())
b'\x80\x03c__main__\nddict\nq\x00)\x81q\x01.'
>>> type(pickle.loads(pickle.dumps(ddict())))
<class '__main__.ddict'>
>>> d = ddict()
>>> d.foo = 'bar'
>>> d.foo
'bar'
>>> pickle.loads(pickle.dumps(d))
{'foo': 'bar'}
pickle
测试 __getstate__
方法 在实例 而不是 class 作为 is the norm for special methods,是改天再讨论。
首先,我想你可能需要区分实例属性和class属性。
在Python官方文档Chapter 11.1.4中关于pickling的内容是:
此类 class 的实例,其 dict 或调用 getstate() 的结果是可腌制(有关详细信息,请参阅“腌制协议”部分)。
因此,当您尝试 pickle class 的实例而不是 class 本身时,您收到的错误消息是 - 事实上,您的 class 定义腌制就好了。
现在要对 class 的对象进行酸洗,问题是您需要先调用父 class 的序列化实现以正确设置。正确的代码是:
In [1]: import pickle
In [2]: class ddict(dict):
...:
...: def __getattr__(self, item):
...: super.__getattr__(self, item)
...: return self[item]
...:
...: def __setattr__(self, key, value):
...: super.__setattr__(self, key, value)
...: self[key] = value
...:
In [3]: d = ddict()
In [4]: d.name = "Sam"
In [5]: d
Out[5]: {'name': 'Sam'}
In [6]: pickle.dumps(d)
Out[6]: b'\x80\x03c__main__\nddict\nq\x00)\x81q\x01X\x04\x00\x00\x00nameq\x02X\x03\x00\x00\x00Samq\x03s}q\x04h\x02h\x03sb.'
我以一种简单的方式扩展了 dict
以使用 d.key
符号而不是 d['key']
:
class ddict(dict):
def __getattr__(self, item):
return self[item]
def __setattr__(self, key, value):
self[key] = value
现在当我尝试 pickle 时,它会调用 __getattr__
来查找 __getstate__
,这既不存在也不必要。使用 __setstate__
:
>>> import pickle
>>> class ddict(dict):
... def __getattr__(self, item):
... return self[item]
... def __setattr__(self, key, value):
... self[key] = value
...
>>> pickle.dumps(ddict())
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in __getattr__
KeyError: '__getstate__'
我该如何修改 class ddict
才能正确选取?
问题不在于 pickle
,而是您的 __getattr__
方法引发了 KeyError
异常,从而违反了预期的约定。您需要修复 __getattr__
方法以引发 AttributeError
异常:
def __getattr__(self, item):
try:
return self[item]
except KeyError:
raise AttributeError(item)
现在 pickle
得到了缺失 __getstate__
自定义挂钩的预期信号。
来自object.__getattr__
documentation:
This method should return the (computed) attribute value or raise an
AttributeError
exception.
(大胆强调我的)。
如果您坚持保留 KeyError
,那么至少您需要跳过以双下划线开头和结尾的名称,并只为那些 AttributeError
加注:
def __getattr__(self, item):
if isinstance(item, str) and item[:2] == item[-2:] == '__':
# skip non-existing dunder method lookups
raise AttributeError(item)
return self[item]
请注意,您可能想给您的 ddict()
subclass an empty __slots__
tuple;您的实例不需要额外的 __dict__
属性映射,因为您正在将属性转移到 key-value 对。这样可以为每个实例节省大量内存。
演示:
>>> import pickle
>>> class ddict(dict):
... __slots__ = ()
... def __getattr__(self, item):
... try:
... return self[item]
... except KeyError:
... raise AttributeError(item)
... def __setattr__(self, key, value):
... self[key] = value
...
>>> pickle.dumps(ddict())
b'\x80\x03c__main__\nddict\nq\x00)\x81q\x01.'
>>> type(pickle.loads(pickle.dumps(ddict())))
<class '__main__.ddict'>
>>> d = ddict()
>>> d.foo = 'bar'
>>> d.foo
'bar'
>>> pickle.loads(pickle.dumps(d))
{'foo': 'bar'}
pickle
测试 __getstate__
方法 在实例 而不是 class 作为 is the norm for special methods,是改天再讨论。
首先,我想你可能需要区分实例属性和class属性。 在Python官方文档Chapter 11.1.4中关于pickling的内容是:
此类 class 的实例,其 dict 或调用 getstate() 的结果是可腌制(有关详细信息,请参阅“腌制协议”部分)。
因此,当您尝试 pickle class 的实例而不是 class 本身时,您收到的错误消息是 - 事实上,您的 class 定义腌制就好了。
现在要对 class 的对象进行酸洗,问题是您需要先调用父 class 的序列化实现以正确设置。正确的代码是:
In [1]: import pickle
In [2]: class ddict(dict):
...:
...: def __getattr__(self, item):
...: super.__getattr__(self, item)
...: return self[item]
...:
...: def __setattr__(self, key, value):
...: super.__setattr__(self, key, value)
...: self[key] = value
...:
In [3]: d = ddict()
In [4]: d.name = "Sam"
In [5]: d
Out[5]: {'name': 'Sam'}
In [6]: pickle.dumps(d)
Out[6]: b'\x80\x03c__main__\nddict\nq\x00)\x81q\x01X\x04\x00\x00\x00nameq\x02X\x03\x00\x00\x00Samq\x03s}q\x04h\x02h\x03sb.'