如何使一个具有__getattr__正确可选的类?

时间:2017-02-16 11:13:52

标签: 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

现在,当我尝试挑选它时,它会调用__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__'

如何修改课程ddict才能正确挑选?

2 个答案:

答案 0 :(得分:4)

问题不是pickle,而是__getattr__方法通过提出KeyError例外来破解预期合同。您需要修改__getattr__方法以引发AttributeError例外:

def __getattr__(self, item):
    try:
        return self[item]
    except KeyError:
        raise AttributeError(item)

现在为pickle提供了缺少__getstate__自定义挂钩的预期信号。

来自object.__getattr__ documentation

  

此方法应返回(计算)属性值或引发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]

请注意,您可能想要提供ddict()子类an empty __slots__ tuple;您不需要在实例上使用额外的__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'}

pickle方法上的__getstate__方法进行<div class="container vertical-center" style="background: url('background.png')";> <div class="row"> <div class="panel panel-primary"> <div class="panel-heading">LOGIN</div> <div class="panel-body"> <div class="input-group"> <span class="input-group-addon" id="basic-addon1"> <span class="glyphicon glyphicon-user"></span> </span> <input type="text" class="form-control" placeholder="Username" /> </div> <br /> <div class="input-group"> <span class="input-group-addon" id="basic-addon2"> <span class="glyphicon glyphicon-lock"></span> </span> <input type="text" class="form-control" placeholder="Password" /> </div> </div> <div class="text-center"> <button type="button" class="btn btn-primary btn-sm" style="align-content: center">Submit</button> </div> <br /> <div class="panel-footer">Trademark</div> </div> </div> </div> 测试,而不是作为is the norm for special methods对该类进行测试,这是另一天的讨论。

答案 1 :(得分:-3)

首先,我认为您可能需要区分实例属性和类属性。 在Python官方文档第11.1.4章中关于酸洗,它说:

此类的实例 dict 或调用 getstate ()的结果是可选的(有关详细信息,请参阅pickle协议一节)。

因此,你得到的错误信息是当你试图挑选一个类的实例时,而不是类本身 - 实际上,你的类定义只会很好。

现在为了挑选类的对象,问题是你需要首先调用父类的序列化实现来正确设置。正确的代码是:

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.'