如何使 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.'