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

[PYTHON] 객체 지향의 품격 : Property 데코레이터와 Descriptor Protocol의 3가지 핵심 차이 및 해결 방법

by Papa Martino V 2026. 3. 4.
728x90

Property 데코레이터와 Descriptor Protocol
Property 데코레이터와 Descriptor Protocol

 

파이썬에서 클래스를 설계할 때 가장 먼저 마주하는 고민은 "인스턴스 변수에 어떻게 안전하게 접근할 것인가?"입니다. 자바(Java)와 같은 언어에서는 Getter와 Setter 메서드를 명시적으로 작성하는 것이 관례지만, 파이썬은 더욱 우아한 @property 데코레이터를 제공합니다. 하지만 이 데코레이터가 내부적으로 어떻게 작동하는지, 그리고 파이썬의 가장 깊은 곳에 위치한 디스크립터 프로토콜(Descriptor Protocol)과 어떤 관계가 있는지 이해하는 개발자는 많지 않습니다. 본 포스팅에서는 파이썬의 속성 관리 메커니즘을 심도 있게 분석하여, 단순한 데코레이터 활용을 넘어 객체의 속성 접근 제어를 완벽하게 장악하는 전문적인 해결 방법을 제시합니다.


1. Property 데코레이터: 파이썬다운 캡슐화

@property는 메서드를 마치 속성(Attribute)처럼 호출할 수 있게 해주는 구문 설탕(Syntactic Sugar)입니다. 이를 통해 사용자에게는 변수처럼 보이지만, 내부적으로는 유효성 검사나 계산 로직을 수행할 수 있는 '계산된 속성'을 구현할 수 있습니다.

  • 장점: 인터페이스의 변경 없이 필드 접근을 제어할 수 있습니다.
  • 한계: 클래스마다 매번 정의해야 하므로 재사용성이 떨어질 수 있습니다.

2. 디스크립터 프로토콜(Descriptor Protocol)의 원리

@property의 화려한 외관 뒤에는 디스크립터(Descriptor)라는 강력한 프로토콜이 존재합니다. 디스크립터는 __get__, __set__, __delete__ 메서드 중 하나 이상을 구현한 객체를 의미합니다. 파이썬 인터프리터는 속성을 찾을 때 이 프로토콜이 구현되어 있다면 해당 메서드를 호출하여 제어권을 넘깁니다.

Property와 Descriptor의 구조적 차이 비교

항목 @property 데코레이터 사용자 정의 Descriptor
주요 목적 단일 속성에 대한 Getter/Setter 구현 여러 클래스/속성 간 재사용 가능한 로직
구현 방식 함수 기반 데코레이터 사용 클래스 기반 매직 메서드 구현
재사용성 낮음 (클래스 내부에 종속) 매우 높음 (독립적인 도구로 활용 가능)
제어 범위 특정 인스턴스 변수에 국한 데이터 유효성 검사 프레임워크 구축 가능

3. Sample Example: 숫자 범위 유효성 검사 해결

단순히 @property를 사용하는 것보다 디스크립터를 사용하여 재사용 가능한 유효성 검사기를 만드는 것이 훨씬 전문적인 해결책입니다.


# 1. 재사용 가능한 Descriptor 클래스 정의
class ValidNumber:
    def __init__(self, minvalue, maxvalue):
        self.minvalue = minvalue
        self.maxvalue = maxvalue
        self.name = None  # __set_name__으로 자동 할당 예정

    def __set_name__(self, owner, name):
        self.name = name

    def __get__(self, instance, owner):
        if instance is None:
            return self
        return instance.__dict__.get(self.name)

    def __set__(self, instance, value):
        if not (self.minvalue <= value <= self.maxvalue):
            raise ValueError(f"{self.name}은(는) {self.minvalue}와 {self.maxvalue} 사이여야 합니다.")
        instance.__dict__[self.name] = value

# 2. 비즈니스 로직 클래스에 적용
class Player:
    level = ValidNumber(1, 100)
    stamina = ValidNumber(0, 200)

    def __init__(self, name, level, stamina):
        self.name = name
        self.level = level
        self.stamina = stamina

# 사용 예시
p = Player("Chaewon", 50, 100)
p.level = 150  # ValueError 발생: level은 1와 100 사이여야 합니다.

4. 전문적인 성능 및 보안 해결 전략

디스크립터를 직접 구현할 때 반드시 고려해야 할 2가지 보안 및 성능 해결 방법이 있습니다.

  1. __set_name__의 활용: 과거 파이썬(3.6 미만)에서는 디스크립터에 속성 이름을 전달하기 위해 메타클래스를 사용해야 했으나, 이제는 __set_name__을 통해 클래스 생성 시점에 속성 이름을 자동으로 인지할 수 있습니다. 이는 코드의 안정성을 높여줍니다.
  2. 데이터 디스크립터 vs 비데이터 디스크립터: __set__ 메서드가 있으면 데이터 디스크립터가 되어 인스턴스 딕셔너리보다 높은 우선순위를 갖습니다. 반면 __get__만 있으면 우선순위가 낮아집니다. 이 차이를 명확히 인지해야 속성 덮어쓰기 버그를 해결할 수 있습니다.

5. 결론: Property는 Descriptor의 특수한 형태

결론적으로 @property는 파이썬이 미리 만들어 둔 '표준 데이터 디스크립터'입니다. 우리가 직접 디스크립터 클래스를 만드는 과정은 @property의 작동 원리를 직접 설계하는 것과 같습니다. 단순한 로직에는 @property를, 프레임워크 수준의 범용적인 속성 제어에는 Descriptor를 선택하는 안목이 필요합니다.


내용 출처 및 기술 참조

  • Python Documentation. "Descriptor HowTo Guide." (The official Python internals guide)
  • Fluent Python 2nd Edition (Luciano Ramalho) - Chapter 23: Attribute Descriptors.
  • Python PEP 487: Simpler customization of class creation.
728x90