119

クラス内の 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
4

6 に答える 6

138

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

アップデート:

現在のドキュメントの「新しいスタイルのクラスの属性アクセスを増やす」というタイトルのセクションに何かがあり、無限再帰を回避するために正確にこれを行うことを推奨しています。

于 2008-12-16T16:26:37.387 に答える
32

__getattr__実際には、代わりに特別な方法を使用したいと思います。

Python ドキュメントからの引用:

__getattr__( self, name)

属性ルックアップが通常の場所で属性を見つけられなかった場合に呼び出されます (つまり、インスタンス属性ではなく、self のクラス ツリーにも見つかりません)。name は属性名です。このメソッドは、(計算された) 属性値を返すか、AttributeError 例外を発生させる必要があります。
属性が通常のメカニズムで見つかった場合、__getattr__()は呼び出されないことに注意してください。__getattr__()(これは、との間の意図的な非対称性__setattr__()です。) これは、効率上の理由と__setattr__()、インスタンスの他の属性にアクセスする方法がないためです。少なくともインスタンス変数については、インスタンス属性ディクショナリに値を挿入しない (代わりに別のオブジェクトに挿入する) ことで、完全な制御を偽造できることに注意してください。を参照してください__getattribute__()新しいスタイルのクラスで実際に完全な制御を取得する方法については、以下のメソッドを参照してください。

注: これが機能するためには、インスタンスに属性がないため、行を削除する必要があります。testself.test=20

于 2008-12-16T22:01:38.053 に答える
13

を使用します__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 になります。理由はあなたに任せます。

__init__Edit2: Greg は、プロパティが読み取り専用であり、メソッドがそれを 20 に設定しようとしたため、例 2 が失敗することを指摘しました。そのためのより完全な例は次のようになります。

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)

明らかに、これはクラスとしてはほとんど役に立たないものですが、先に進むためのアイデアを与えてくれます。

于 2008-12-16T16:32:36.267 に答える
11

メソッドはどのように__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__

  • raise AttributeError__getattr__呼び出されます (実装されている場合)
  • それから何かを返す
    • super親 (おそらくobjectの) 実装を呼び出すために使用
    • 呼び出し__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__ます。これが、再帰エラーが発生する理由です。name が"__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
于 2015-02-05T23:48:48.703 に答える
5

より信頼性の高いバージョンは次のとおりです。

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 __メソッド (他の祖先がオーバーライドしない場合)。

于 2013-05-29T10:18:19.013 に答える