[英]Python: inconsistence in the way you define the function __setattr__?
考虑这段代码:
class Foo1(dict):
def __getattr__(self, key): return self[key]
def __setattr__(self, key, value): self[key] = value
class Foo2(dict):
__getattr__ = dict.__getitem__
__setattr__ = dict.__setitem__
o1 = Foo1()
o1.x = 42
print(o1, o1.x)
o2 = Foo2()
o2.x = 42
print(o2, o2.x)
我期望同样的 output。 但是,使用 CPython 2.5、2.6(类似于 3.2)我得到:
({'x': 42}, 42)
({}, 42)
使用 PyPy 1.5.0,我得到了预期的 output:
({'x': 42}, 42)
({'x': 42}, 42)
哪个是“正确的”output? (或者根据 Python 文档,output 应该是什么?)
这是 CPython 的错误报告。
我怀疑它与查找优化有关。 从源代码:
/* speed hack: we could use lookup_maybe, but that would resolve the
method fully for each attribute lookup for classes with
__getattr__, even when the attribute is present. So we use
_PyType_Lookup and create the method only when needed, with
call_attribute. */
getattr = _PyType_Lookup(tp, getattr_str);
if (getattr == NULL) {
/* No __getattr__ hook: use a simpler dispatcher */
tp->tp_getattro = slot_tp_getattro;
return slot_tp_getattro(self, name);
}
快速路径不会在 class 字典中查找它。
因此,获得所需功能的最佳方法是在 class 中放置一个覆盖方法。
class AttrDict(dict):
"""A dictionary with attribute-style access. It maps attribute access to
the real dictionary. """
def __init__(self, *args, **kwargs):
dict.__init__(self, *args, **kwargs)
def __repr__(self):
return "%s(%s)" % (self.__class__.__name__, dict.__repr__(self))
def __setitem__(self, key, value):
return super(AttrDict, self).__setitem__(key, value)
def __getitem__(self, name):
return super(AttrDict, self).__getitem__(name)
def __delitem__(self, name):
return super(AttrDict, self).__delitem__(name)
__getattr__ = __getitem__
__setattr__ = __setitem__
def copy(self):
return AttrDict(self)
我发现它按预期工作。
这是一个已知的(也许不是那么好)有据可查的差异。 PyPy 不区分函数和内置函数。 在 CPython 中,当存储在 class(具有 __get__)上时,函数会被绑定为未绑定的方法,而内置函数则不会(它们不同)。
然而,在 PyPy 下,内置函数与 python 函数完全相同,因此解释器无法区分它们并将它们视为 python 级函数。 我认为这被定义为实现细节,尽管在 python-dev 上有一些关于消除这个特殊差异的讨论。
干杯,
菲哈尔
请注意以下事项:
>>> dict.__getitem__ # it's a 'method'
<method '__getitem__' of 'dict' objects>
>>> dict.__setitem__ # it's a 'slot wrapper'
<slot wrapper '__setitem__' of 'dict' objects>
>>> id(dict.__dict__['__getitem__']) == id(dict.__getitem__) # no bounding here
True
>>> id(dict.__dict__['__setitem__']) == id(dict.__setitem__) # or here either
True
>>> d = {}
>>> dict.__setitem__(d, 1, 2) # can be called directly (since not bound)
>>> dict.__getitem__(d, 1) # same with this
2
现在我们可以包装它们(即使没有它__getattr__
也可以工作):
class Foo1(dict):
def __getattr__(self, key): return self[key]
def __setattr__(self, key, value): self[key] = value
class Foo2(dict):
"""
It seems, 'slot wrappers' are not bound when present in the __dict__
of a class and retrieved from it via instance (or class either).
But 'methods' are, hence simple assignment works with __setitem__
in your original example.
"""
__setattr__ = lambda *args: dict.__setitem__(*args)
__getattr__ = lambda *args: dict.__getitem__(*args) # for uniformity, or
#__getattr__ = dict.__getitem__ # this way, i.e. directly
o1 = Foo1()
o1.x = 42
print(o1, o1.x)
o2 = Foo2()
o2.x = 42
print(o2, o2.x)
这使:
>>>
({'x': 42}, 42)
({'x': 42}, 42)
有问题的行为背后的机制(可能,我不是专家)在 Python 的“干净”子集之外(如“Learning Python”或“Python in a nutshell”等详尽的书籍中所述,并且在 python 中有一些松散的规定。 org) 并且属于由实现“按原样”记录的语言部分(并且受到(相当)频繁的更改)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.