본문 바로가기
Artificial Intelligence/60. Python

[PYTHON] 객체 속성 제어의 2가지 핵심 : __getattr__와 __getattribute__의 호출 우선순위 및 무한 루프 방지 방법

by Papa Martino V 2026. 2. 23.
728x90

__getattr__와 __getattribute__
__getattr__와 __getattribute__

 

 

파이썬은 고도의 동적 언어로, 객체의 속성(Attribute)에 접근할 때 내부적으로 복잡하면서도 정교한 메커니즘을 거칩니다. 개발자는 __getattr____getattribute__라는 매직 메서드를 통해 이 접근 과정을 가로채거나 수정할 수 있습니다. 하지만 이 두 메서드는 호출되는 시점과 방식이 판이하게 다르며, 잘못 구현할 경우 시스템을 중단시키는 '무한 루프(Infinite Recursion)'에 빠지기 쉽습니다. 본 가이드에서는 파이썬 시니어 개발자가 반드시 숙지해야 할 속성 접근의 우선순위와 안전한 구현 방법을 심층 분석합니다.


1. 매직 메서드의 정의와 결정적 차이

속성 접근을 제어하는 두 메서드는 이름은 비슷하지만, 파이썬 인터프리터에 의해 처리되는 논리가 완전히 다릅니다.

  • __getattribute__(self, name): 객체의 속성에 접근할 때 무조건적으로 가장 먼저 호출됩니다. 속성이 __dict__에 존재하든, 클래스 변수이든 상관없이 모든 접근을 가로챕니다.
  • __getattr__(self, name): 일반적인 속성 검색 결과가 AttributeError를 발생시킬 때만, 즉 '속성을 찾지 못했을 때' 마지막 수단으로 호출됩니다.

2. 호출 우선순위 및 동작 메커니즘 비교

파이썬이 속성 obj.x를 찾는 과정을 단계별로 비교하면 다음과 같습니다.

비교 항목 __getattribute__ __getattr__
호출 시점 모든 속성 접근 시 (필수 호출) 속성을 찾을 수 없을 때만 (선택 호출)
우선순위 최상위 (1순위) 최하위 (모든 시도 실패 후)
무한 루프 위험 매우 높음 (내부 접근 시 발생) 낮음 (존재하지 않는 속성 재귀 호출 시 발생)
주요 목적 강력한 접근 제어, 보안, 프록시 구현 기본값 제공, 동적 속성 생성, 지연 로딩

3. 무한 루프의 원인과 치명적인 실수

가장 빈번하게 발생하는 실수는 __getattribute__ 내부에서 다시 self.name이나 self.__dict__를 사용하여 속성에 접근하는 것입니다. 이 경우 인터프리터는 다시 __getattribute__를 호출하게 되어 스택 오버플로(Stack Overflow)가 발생합니다.

해결 방법: super()의 활용

무한 루프를 방지하기 위해서는 반드시 상위 클래스의 메서드인 super().__getattribute__(name)를 호출하여 파이썬의 표준 속성 검색 엔진을 이용해야 합니다.


4. [Sample Example] 안전한 속성 제어 구현

다음은 동적 로깅 기능을 갖춘 안전한 속성 접근 클래스 예제입니다.


class SmartProxy:
    def __init__(self, target):
        # __setattr__ 오버라이딩을 피하기 위해 직접 __dict__ 조작
        object.__setattr__(self, "_target", target)

    def __getattribute__(self, name):
        print(f"로그: '{name}' 속성에 접근을 시도합니다.")
        # 무한 루프 방지를 위해 super() 사용
        try:
            return super().__getattribute__(name)
        except AttributeError:
            # 여기서 getattr 호출로 넘김
            return self.__getattr__(name)

    def __getattr__(self, name):
        print(f"경고: '{name}' 속성이 존재하지 않습니다. 동적 처리를 실행합니다.")
        return f"Dynamic value for {name}"

# 실행 예시
proxy = SmartProxy({"data": 100})
print(proxy._target)    # __getattribute__ 통과 후 출력
print(proxy.unknown)    # __getattribute__ -> __getattr__ 순으로 호출

5. 실무 적용 시 고려해야 할 3가지 팁

  1. 성능 고려: __getattribute__는 모든 접근에 오버헤드를 발생시킵니다. 단순히 속성이 없을 때의 처리가 목적이라면 __getattr__가 훨씬 효율적입니다.
  2. 상태 관리: 인스턴스 초기화(__init__) 중에 속성을 설정할 때도 __getattribute__가 호출될 수 있음을 유의하십시오.
  3. 내장 함수와의 관계: len()이나 str() 같은 내장 함수는 효율성을 위해 인스턴스 딕셔너리보다 클래스 슬롯을 먼저 참조하므로, 이 매직 메서드들이 기대한 대로 작동하지 않을 수 있습니다.

6. 내용 출처

  • Python Official Documentation - Customizing attribute access (Section 3.3.2)
  • "Fluent Python" by Luciano Ramalho - Chapter 19: Dynamic Attributes and Properties
  • CPython Source Code - Objects/typeobject.c
728x90