무한 재귀 오류없이 __getattribute__을 어떻게 구현합니까?


101

클래스의 한 변수에 대한 액세스를 재정의하고 싶지만 다른 모든 변수는 정상적으로 반환됩니다. 이 작업을 어떻게 수행 __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

답변:


127

self.__dict__내부 속성 에 액세스하려는 시도가 다시 __getattribute__호출 되기 때문에 재귀 오류가 발생 __getattribute__합니다. 대신 object's 를 사용 __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
.. 항상 객체를 사용하고 싶습니까? 다른 클래스에서 상속하면 어떻게됩니까?
Greg는

1
예, 클래스를 만들고 직접 작성하지 않을 때 마다 개체에서 제공 하는 getattribute 를 사용 합니다.
Egil

20
super ()를 사용하여 파이썬이 기본 클래스에서 찾은 첫 번째 getattribute 메서드 를 사용하는 것이 더 낫지 않습니까? -super(D, self).__getattribute__(name)
gepatino

10
또는 super().__getattribute__(name)Python 3에서 사용할 수 있습니다.
jeromej

25

사실, __getattr__대신 특별한 방법 을 사용하고 싶다고 생각합니다 .

Python 문서에서 인용 :

__getattr__( self, name)

속성 조회가 일반적인 위치에서 속성을 찾지 못했을 때 호출됩니다 (즉, 인스턴스 속성이 아니고 self에 대한 클래스 트리에서도 찾을 수 없음). name은 속성 이름입니다. 이 메서드는 (계산 된) 속성 값을 반환하거나 AttributeError 예외를 발생시켜야합니다.
일반 메커니즘을 통해 속성을 찾은 경우는 __getattr__()호출되지 않습니다. (이 사이의 의도적 인 비대칭이다 __getattr__()__setattr__().) 그렇지 않으면 때문에 효율성을 이유로 모두를 수행하고 __setattr__()인스턴스의 액세스 다른 속성에 방법이 없습니다. 적어도 인스턴스 변수의 경우 인스턴스 속성 사전에 값을 삽입하지 않고 대신 다른 개체에 삽입하여 전체 제어를 위조 할 수 있습니다. 참조__getattribute__() 새로운 스타일의 클래스에서 실제로 완전한 제어를 얻는 방법은 아래의 방법입니다.

참고 :이 작업을 수행하려면, 인스턴스가해야 하지test라인이 있으므로, 속성을 self.test=20제거해야합니다.


2
실제로 OP 코드의 특성에 따라 __getattr__for 재정의 test는 항상 "일반적인 장소에서"찾을 수 있기 때문에 쓸모가 없습니다.
Casey Kuball 2012

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__검색에서 호출됩니다 ... yada yada yada__dict____getattribute__

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

편집 :의 인스턴스는 각 경우에 D다른 값을 갖습니다 test. 첫 번째 경우에는 d.test20이되고 두 번째 경우에는 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

진실. 세 번째 예제로 수정하겠습니다. 잘 발견되었습니다.
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__합니다.

이 방법의 사용은 다소 드뭅니다. 표준 라이브러리에는 두 가지 정의 만 있습니다.

$ grep -Erl  "def __getattribute__\(self" cpython/Lib | grep -v "/test/"
cpython/Lib/_threading_local.py
cpython/Lib/importlib/util.py

모범 사례

단일 속성에 대한 액세스를 프로그래밍 방식으로 제어하는 ​​적절한 방법은 property. 클래스 D는 다음과 같이 작성되어야합니다 (선택적으로 의도 된 동작을 복제하기 위해 setter 및 deleter 사용).

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.