web-dev-qa-db-ja.com

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

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

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 というタイトルのセクションに何かがあります。無限再帰を避けるためにこれを正確に行うことをお勧めします。

111
Egil

実際、代わりに___getattr___特別なメソッドを使用したいと思います。

Python docs:

__getattr__( self, name)

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

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

23
tzot

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__を使用すると、実際の属性を見つけることができます。

16
ttepasse

使用してよろしいですか__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)

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

13
Singletoned

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

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
ElmoVanKielmo

__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
3
Aaron Hall