너도 할 수 있는, 너도밤나무 코딩

[Python] 디스크립터(Descriptor) 완전 정복 본문

프로그래밍 언어/파이썬

[Python] 디스크립터(Descriptor) 완전 정복

곡마일장 2025. 9. 11. 08:10
반응형

파이썬을 깊이 공부하다 보면, 클래스 속성의 접근과 저장 과정을 세밀하게 제어할 필요가 생깁니다. 이때 활용할 수 있는 강력한 도구가 바로 디스크립터(Descriptor)입니다. 디스크립터를 이해하면 property, @staticmethod, ORM 필드 정의 등 여러 고급 기능이 내부적으로 어떻게 동작하는지 알 수 있습니다.

이번 글에서는 디스크립터의 기본 개념부터 구현 방법, 실전 활용, 설계 팁까지 블로그용으로 자세히 다뤄보겠습니다.


1. 디스크립터란 무엇인가?

디스크립터는 클래스 속성의 접근, 할당, 삭제를 제어할 수 있는 객체를 말합니다. 파이썬은 __get__, __set__, __delete__ 메서드를 가진 객체를 디스크립터로 인식합니다.

  • __get__(self, instance, owner): 속성을 읽을 때 호출
  • __set__(self, instance, value): 속성에 값을 할당할 때 호출
  • __delete__(self, instance): 속성을 삭제할 때 호출
class DescriptorExample:
    def __get__(self, instance, owner):
        print("값을 가져옵니다.")
        return 42

class MyClass:
    attribute = DescriptorExample()

obj = MyClass()
print(obj.attribute)

출력:

값을 가져옵니다.
42

obj.attribute를 읽는 순간 __get__이 호출되어 42를 반환합니다.


2. 디스크립터의 종류

디스크립터는 구현된 메서드에 따라 크게 3가지로 나눌 수 있습니다.

  1. 데이터 디스크립터: __set__ 또는 __delete__를 구현한 경우
  2. 비데이터 디스크립터: __get__만 구현한 경우
  3. 프로퍼티(Property): 내장 디스크립터, @property 사용
class IntegerField:
    def __init__(self):
        self.value = 0

    def __get__(self, instance, owner):
        return self.value

    def __set__(self, instance, value):
        if not isinstance(value, int):
            raise TypeError("정수만 할당할 수 있습니다.")
        self.value = value

class MyClass:
    number = IntegerField()

obj = MyClass()
obj.number = 10  # 정상
# obj.number = 'string'  # TypeError 발생

3. 프로퍼티와 디스크립터

@property는 디스크립터의 간편한 형태입니다. 내부적으로 __get__, __set__, __delete__를 구현한 객체를 반환합니다.

class MyClass:
    def __init__(self):
        self._x = 0

    @property
    def x(self):
        return self._x

    @x.setter
    def x(self, value):
        if value < 0:
            raise ValueError("0 이상만 가능합니다.")
        self._x = value

obj = MyClass()
obj.x = 10
print(obj.x)  # 10
# obj.x = -1  # ValueError 발생

즉, 디스크립터를 직접 구현하지 않아도 property를 사용하면 속성 접근 제어가 가능합니다.


4. 실전 활용 사례

(1) ORM 필드 정의

Django, SQLAlchemy 등 ORM에서는 디스크립터를 활용해 모델 필드와 데이터베이스 컬럼을 연결합니다.

class Field:
    def __init__(self, column_type):
        self.column_type = column_type
        self.value = None

    def __get__(self, instance, owner):
        return self.value

    def __set__(self, instance, value):
        print(f"{self.column_type} 컬럼에 값 설정: {value}")
        self.value = value

class User:
    name = Field("TEXT")

user = User()
user.name = "Alice"  # TEXT 컬럼에 값 설정: Alice

(2) 속성 접근 로깅

디스크립터를 사용해 모든 속성 접근을 기록할 수도 있습니다.

class LoggedAttribute:
    def __init__(self, name):
        self.name = name

    def __get__(self, instance, owner):
        print(f"{self.name} 읽음")
        return instance.__dict__.get(self.name, None)

    def __set__(self, instance, value):
        print(f"{self.name} 변경: {value}")
        instance.__dict__[self.name] = value

class MyClass:
    attr = LoggedAttribute('attr')

obj = MyClass()
obj.attr = 100  # attr 변경: 100
print(obj.attr)  # attr 읽음

5. 설계 팁

  • 속성 제약: 특정 타입만 허용하거나 값 범위를 제한할 때 유용
  • 코드 재사용: 여러 클래스에서 공통된 속성 제어 로직을 공유할 수 있음
  • ORM/프레임워크 활용: 필드 정의, 유효성 검증, 접근 로깅 등에서 활용
  • 가독성 유지: 복잡한 로직은 데코레이터 또는 property로도 해결 가능

6. 결론

디스크립터는 파이썬 속성의 접근과 할당을 세밀하게 제어할 수 있는 강력한 도구입니다. 단순히 property만 사용하는 것보다 훨씬 유연하며, ORM, 데이터 검증, 로깅 등 다양한 실무 활용이 가능합니다.

디스크립터를 이해하면 파이썬의 내부 동작 원리를 깊이 이해할 수 있고, 고급 객체지향 설계를 구현할 때 큰 도움이 됩니다. 따라서 파이썬 심화 문법을 학습하는 개발자라면 반드시 익혀야 할 개념 중 하나입니다.

반응형