Python:関数の定義方法__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)
同じ出力が期待されます。ただし、Cpython 2.5、2.6(同様に3.2で)では、次のようになります。
({'x': 42}, 42)
({}, 42)
Pypy 1.5.0を使用すると、予想される出力を取得します。
({'x': 42}, 42)
({'x': 42}, 42)
「正しい」出力はどれですか? (または、Pythonドキュメントに従って出力はどうあるべきですか?)
ここ 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 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は、関数とBuiltin関数を区別しません。 cpythonでは、クラスに保存された場合(__get__を持っている)、ビルトイン関数はそうではありません(それらは異なっています)。
ただし、Pypyでは、Builtin機能は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の「クリーン」サブセットの外側(「学習Python」や「Python in a Nutshell」などの徹底的な本に記録されており、Pythonでややゆるく指定されているように。 org)および実装によって「AS-IS」が文書化された言語の部分に関係します((むしろ)頻繁な変更の対象となります)。