無限再帰エラーなしで__getattribute__を実装するにはどうすればよいですか?


100

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

回答:


126

self.__dict__内部の属性にアクセスしようと__getattribute__すると__getattribute__再びが呼び出されるため、再帰エラーが発生します。あなたが使用している場合はobjectS」__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

更新:

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


1
面白い。そこで何をしてるの?なぜオブジェクトに変数があるのですか?
グレッグ、

1
..そして常にオブジェクトを使いたいですか?他のクラスから継承した場合はどうなりますか?
グレッグ

1
はい。クラスを作成して独自のクラスを作成しない場合は、オブジェクトによって提供されるgetattributeを使用します。
Egil、

19
super()を使用して、基本クラスでpythonが検出する最初のgetattributeメソッドを使用する方がよいのではないですか。-super(D, self).__getattribute__(name)
gepatino

10
それとも、ただ使用することができますsuper().__getattribute__(name)。3. Pythonで
jeromej

25

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

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

__getattr__( self, name)

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

注:仕事へのこのため、インスタンスはすべきではない持っているtest属性を、その行がself.test=20削除されなければなりません。


2
実際には、OPのコードの性質上、常に「通常の場所で」それを見つけるため、__getattr__forのオーバーライドはtest役に立たないでしょう。
Casey Kuball

1
関連するPythonドキュメントへの現在のリンク(回答で参照されているものとは異なるようです):docs.python.org/3/reference/datamodel.html#object.__getattr__
CrepeGoat

17

Python言語リファレンス:

このメソッドでの無限再帰を回避するために、その実装は常に同じ名前で基本クラスメソッドを呼び出して、必要な属性にアクセスする必要があります 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__を使用すると、実際の属性を見つけるのに役立ちます。


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

編集:のインスタンスDtest、それぞれの場合に異なる値を持つことに注意してください。最初のケースでd.testは20になります。2番目のケースでは0になります。理由を解明するのは、あなたに任せます。

Edit2:Gregは、プロパティは読み取り専用であり、__init__メソッドがそれを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)

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


でも、クラスを運営しているときは静かに動作しません。ファイル "Script1.py"、5行目、init self.test = 20 AttributeError:ca n't set attribute
Greg

そうだね。これを3番目の例として修正します。よく見つかりました。
Singletoned

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


5

方法はどのように__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__。これが、再帰エラーが発生する理由です。名前がかどうかを確認して回避策として"__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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.