クラス内の1つの変数へのアクセスをオーバーライドしたいが、他のすべての変数を通常どおり返します。 __getattribute__
でこれを達成するにはどうすればよいですか?
私は次のことを試しました(これは私がやろうとしていることを示すはずです)が、再帰エラーが発生します:
class D(object):
def __init__(self):
self.test=20
self.test2=21
def __getattribute__(self,name):
if name=='test':
return 0.
else:
return self.__dict__[name]
>>> print D().test
0.0
>>> print D().test2
...
RuntimeError: maximum recursion depth exceeded in cmp
self.__dict__
内の__getattribute__
属性にアクセスしようとすると__getattribute__
が再度呼び出されるため、再帰エラーが発生します。代わりにobject
の__getattribute__
を使用すると、動作します:
class D(object):
def __init__(self):
self.test=20
self.test2=21
def __getattribute__(self,name):
if name=='test':
return 0.
else:
return object.__getattribute__(self, name)
これは、object
(この例では)が基本クラスであるため機能します。 __getattribute__
の基本バージョンを呼び出すことで、以前の再帰的な地獄を回避できます。
Foo.pyのコードを使用したIpython出力:
In [1]: from foo import *
In [2]: d = D()
In [3]: d.test
Out[3]: 0.0
In [4]: d.test2
Out[4]: 21
更新:
現在のドキュメントの New-style classes というタイトルのセクションに何かがあります。無限再帰を避けるためにこれを正確に行うことをお勧めします。
実際、代わりに___getattr__
_特別なメソッドを使用したいと思います。
Python docs:
__getattr__( self, name)
属性ルックアップが通常の場所で属性を見つけられなかった場合に呼び出されます(つまり、インスタンス属性ではなく、自己のクラスツリーでも見つかりません)。 nameは属性名です。このメソッドは、(計算された)属性値を返すか、AttributeError例外を発生させる必要があります。
通常のメカニズムで属性が見つかった場合、__getattr__()
は呼び出されないことに注意してください。 (これは__getattr__()
と__setattr__()
の間の意図的な非対称です。)これは、効率上の理由と、そうでなければ__setattr__()
が他の属性にアクセスする方法がないために行われますインスタンス。少なくともインスタンス変数については、インスタンス属性ディクショナリに値を挿入せずに(代わりに別のオブジェクトに値を挿入することで)トータルコントロールを偽造できることに注意してください。新しいスタイルのクラスで実際に完全な制御を取得する方法については、以下の__getattribute__()
メソッドを参照してください。
注:これが機能するためには、インスタンスはnotにtest
属性を持つ必要があるため、行_self.test=20
_を削除する必要があります。
このメソッドでの無限再帰を回避するために、その実装は常に同じ名前の基本クラスメソッドを呼び出して、必要な属性(
object.__getattribute__(self, name)
など)にアクセスする必要があります。
意味:
def __getattribute__(self,name):
...
return self.__dict__[name]
__dict__
という属性を呼び出しています。それは属性であるため、__getattribute__
は...を呼び出す__dict__
を呼び出す__getattribute__
の検索で呼び出されます... yada yada yada
return object.__getattribute__(self, name)
基本クラス__getattribute__
を使用すると、実際の属性を見つけることができます。
使用してよろしいですか__getattribute__
?実際に何を達成しようとしていますか?
あなたが尋ねることをする最も簡単な方法は:
class D(object):
def __init__(self):
self.test = 20
self.test2 = 21
test = 0
または:
class D(object):
def __init__(self):
self.test = 20
self.test2 = 21
@property
def test(self):
return 0
編集:D
のインスタンスは、それぞれの場合にtest
の異なる値を持つことに注意してください。最初のケースではd.test
は20になり、2番目に0になります。理由を判断するためにあなたに任せます。
Edit2:Gregは、プロパティが読み取り専用であり、__init__
メソッドは20に設定しようとしました。そのためのより完全な例は次のとおりです。
class D(object):
def __init__(self):
self.test = 20
self.test2 = 21
_test = 0
def get_test(self):
return self._test
def set_test(self, value):
self._test = value
test = property(get_test, set_test)
明らかに、これはクラスとしてはほとんどまったく役に立ちませんが、先へ進むためのアイデアを与えてくれます。
より信頼性の高いバージョンは次のとおりです。
class D(object):
def __init__(self):
self.test = 20
self.test2 = 21
def __getattribute__(self, name):
if name == 'test':
return 0.
else:
return super(D, self).__getattribute__(name)
__getattribute__メソッドを親クラスから呼び出し、最終的にオブジェクトにフォールバックします。__getattribute__他の祖先がオーバーライドしない場合のメソッド。
__getattribute__
メソッドはどのように使用されますか?
通常のドット検索の前に呼び出されます。 AttributeError
が発生する場合、__getattr__
を呼び出します。
この方法の使用はかなりまれです。標準ライブラリには2つの定義しかありません。
$ grep -Erl "def __getattribute__\(self" cpython/Lib | grep -v "/test/"
cpython/Lib/_threading_local.py
cpython/Lib/importlib/util.py
ベストプラクティス
単一の属性へのアクセスをプログラムで制御する適切な方法は、 property
を使用することです。クラスD
は、次のように記述する必要があります(オプションのセッターと削除機能を使用して、明らかな意図した動作を再現します)。
class D(object):
def __init__(self):
self.test2=21
@property
def test(self):
return 0.
@test.setter
def test(self, value):
'''dummy function to avoid AttributeError on setting property'''
@test.deleter
def test(self):
'''dummy function to avoid AttributeError on deleting property'''
そして使用法:
>>> o = D()
>>> o.test
0.0
>>> o.test = 'foo'
>>> o.test
0.0
>>> del o.test
>>> o.test
0.0
プロパティはデータ記述子であるため、通常のドット付きルックアップアルゴリズムで最初に検索されるものです。
__getattribute__
のオプション__getattribute__
を介してすべての属性のルックアップを絶対に実装する必要がある場合、いくつかのオプションがあります。
AttributeError
を発生させ、__getattr__
を呼び出します(実装されている場合)super
を使用して、親(おそらくobject
's)実装を呼び出す__getattr__
の呼び出し例えば:
class NoisyAttributes(object):
def __init__(self):
self.test=20
self.test2=21
def __getattribute__(self, name):
print('getting: ' + name)
try:
return super(NoisyAttributes, self).__getattribute__(name)
except AttributeError:
print('oh no, AttributeError caught and reraising')
raise
def __getattr__(self, name):
"""Called if __getattribute__ raises AttributeError"""
return 'close but no ' + name
>>> n = NoisyAttributes()
>>> nfoo = n.foo
getting: foo
oh no, AttributeError caught and reraising
>>> nfoo
'close but no foo'
>>> n.test
getting: test
20
そして、この例は、元々望んでいたことをどのように行うかを示しています。
class D(object):
def __init__(self):
self.test=20
self.test2=21
def __getattribute__(self,name):
if name=='test':
return 0.
else:
return super(D, self).__getattribute__(name)
そして、このように振る舞います:
>>> o = D()
>>> o.test = 'foo'
>>> o.test
0.0
>>> del o.test
>>> o.test
0.0
>>> del o.test
Traceback (most recent call last):
File "<pyshell#216>", line 1, in <module>
del o.test
AttributeError: test
コメント付きのコード。 __getattribute__
にselfのドットルックアップがあります。これが、再帰エラーを受け取る理由です。名前が"__dict__"
かどうかを確認し、回避策としてsuper
を使用できますが、それは__slots__
をカバーしません。これは読者の演習として残しておきます。
class D(object):
def __init__(self):
self.test=20
self.test2=21
def __getattribute__(self,name):
if name=='test':
return 0.
else: # v--- Dotted lookup on self in __getattribute__
return self.__dict__[name]
>>> print D().test
0.0
>>> print D().test2
...
RuntimeError: maximum recursion depth exceeded in cmp