如何制作具有 __getattr__ 正确选择的类?

Mic*_*ael 2 python pickle

dict以一种简单的方式扩展为使用d.key符号而不是直接访问它的值d['key']

class ddict(dict):

    def __getattr__(self, item):
        return self[item]

    def __setattr__(self, key, value):
        self[key] = value
Run Code Online (Sandbox Code Playgroud)

现在,当我尝试腌制它时,它会调用__getattr__find __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__'
Run Code Online (Sandbox Code Playgroud)

我必须如何修改类ddict才能正确选择?

Mar*_*ers 8

问题不pickle在于您的__getattr__方法通过引发KeyError异常打破了预期的契约。您需要修复您的__getattr__方法以引发AttributeError异常:

def __getattr__(self, item):
    try:
        return self[item]
    except KeyError:
        raise AttributeError(item)
Run Code Online (Sandbox Code Playgroud)

现在pickle给出缺少__getstate__自定义钩子的预期信号。

object.__getattr__文档

此方法应返回(计算的)属性值或引发AttributeError异常

(粗体强调我的)。

如果您坚持保留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]
Run Code Online (Sandbox Code Playgroud)

请注意,您可能希望为您的ddict()子类提供一个空__slots__元组;您不需要__dict__实例上的额外属性映射,因为您将属性转移到键值对。这为每个实例节省了大量内存。

演示:

>>> 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'}
Run Code Online (Sandbox Code Playgroud)

pickle为测试__getstate__方法上的实例,而不是类的是特殊的方法规范,是另一天的讨论。