CS

[Python] 속성 디스크립터

왕밤빵도라에몽 2025. 6. 1. 13:38
728x90

디스크립터

디스크립터를 이용하면 여러 속성에 대한 동일한 접근 논리를 재사용할 수 있다.
디스크립터는 __get__(), __set__(), __delete()__ 메서드로 구성된 동적 프로토콜을 구현하는 클래스다.

즉, 어떤 클래스가 이 메서드들 중 하나라도 구현하고 있으면,
그 클래스의 인스턴스는 디스크립터 역할을 하게 된다.

property 클래스는 이 디스크립터 프로토콜을 완벽하게 따르는 대표적인 예이다.
실제로 보는 대부분의 디스크립터는 __get__()__set()__ 메서드만 구현한다.

관련 용어

  • 디스크립터 클래스: 디스크립터 프로토콜 구현 클래스
  • 관리 대상 클래스: 디스크립터 객체를 클래스 속성으로 선언하는 클래스
  • 디스크립터 인스턴스: 관리 대상 클래스의 속성으로 선언된, 디스크립터 클래스의 인스턴스
  • 관리 대상 인스턴스: 관리 대상 클래스의 인스턴스
  • 저장소 인스턴스: 특정 인스턴스의 관리 대상 속성값을 보관할 관리 대상 인스턴스의 속성
  • 관리 대상 속성: 디스크립터 인스턴스가 관리하는 관리 대상 클래스의 공개 속성

예제

간단한 디스크립터

class Quantity:

    def __init__(self, storage_name):
        self.storage_name = storage_name

    def __set__(self, instance, value):
        if value > 0:
            instance.__dict__[self.storage_name] = value
         else:
            msg = f'{self.storage_name} must be > 0'
            raise ValueError(msg)

    def __get__(self, instance, owner):
        return instance.__dict__[self.storage_name]
class LineItem:
    weight = Quantity('weight')
    price = Quantity('price')

    def __init__(self, description, weight, price):
        self.description = description
        self.weight = weight
        self.price = price

    def subtotal(self):
        return self.weight = self.price

저장소 속성명 자동으로 붙이도록 수정

속성명을 반복 입력하지 않도록 storage_name을 설정하는 __set_name__() 메서드를 구현하자

class Quantity:
    def __set_name__(self, owner, name):    
        self.storage_name = name

    def __set__(self, instance, value):
        if value > 0:
            instance.__dict__[self.storage_name] = value
        else:
            msg = f'{self.storage_name} must be > 0'
            raise ValueError(msg)
---

```python
class LineItem:
    weight = Quantity()   # 생성자에 관리 대상 속성명을 전달할 필요가 없어짐
    price = Quantity()

    def __init__(self, description, weight, price):
        self.description = description
        self.weight = weight
        self.price = price

    def subtotal(self):
        return self.weight = self.price

오버라이딩 디스크립터와 논오버라이딩 디스크립터

__set__() 메서드의 유무에 따라 디스크립터의 작동 방식이 두 가지로 나뉨

오버라이딩 디스크립터

  • __get__()__set__() 중 하나 이상을 구현한 디스크립터
  • 인스턴스 딕셔너리(__dict__)보다 우선
  • 즉, 인스턴스에 동일한 이름의 속성이 있어도, 디스크립터가 항상 우선 적용됨
class Over:
    def __get__(self, instance, owner):
        print("📥 Overriding __get__")
        return 42

    def __set__(self, instance, value):
        print(f"📤 Overriding __set__: {value}")
class MyClass:
    attr = Over()

obj = MyClass()
obj.attr         # 📥 Overriding __get__
obj.attr = 100   # 📤 Overriding __set__
obj.__dict__['attr'] = 999
obj.attr         # 여전히 📥 Overriding __get__ (999 무시됨)

논오버라이딩 디스크립터

  • __get__()만 구현한 디스크립터
  • 인스턴스 딕셔너리에 동일한 이름의 속성이 있으면, 디스크립터가 무시됨
class NonOver:
    def __get__(self, instance, owner):
        print("📥 Non-overriding __get__")
        return 100
class MyClass:
    attr = NonOver()

obj = MyClass()
obj.attr             # 📥 Non-overriding __get__
obj.attr = 500       # 인스턴스 딕셔너리에 attr 키가 생김
print(obj.attr)      # 500 (디스크립터 무시됨)

디스크립터 사용팁

  • 코드를 간결하게 유지하기 위해 property()를 사용해라
  • 읽기 전용 디스크립터는 __set__()을 구현해야 한다
  • 검증 디스크립터는 __set__()만 사용할 수 있다
  • 캐싱은 __get__()으로만 효율적으로 구현할 수 있다
  • 특별 메서드 이외의 메서드는 인스턴스 속성에 가려질 수 있다

References

-『 전문가를 위한 파이썬(2판) 』, 루시아누 하말류, 한빛미디어, Part 5 - Chapter 23


  • 학부 1학년 때 객체지향프로그래밍을 수강하면서 배웠던 내용을 이렇게 자세하게 다시 한 번 접하게 된다.
728x90