Python 디스크립터(Descriptor)는 __get__, __set__, __delete__ 메서드 중 하나 이상을 구현한 객체로, 속성 접근을 커스터마이즈한다. property, classmethod, staticmethod가 모두 디스크립터로 구현되어 있다.
디스크립터 프로토콜
python
class Descriptor:
def __get__(self, obj, objtype=None):
# obj: 인스턴스 (클래스에서 접근 시 None)
# objtype: 클래스
...
def __set__(self, obj, value):
...
def __delete__(self, obj):
...
- •데이터 디스크립터:
__set__ 또는 __delete__ 구현 → 인스턴스 __dict__보다 우선 - •비데이터 디스크립터:
__get__만 구현 → 인스턴스 __dict__에 가려짐
타입 검증 디스크립터
python
class TypedAttr:
def __set_name__(self, owner, name):
self.name = name
self.private = f'_{name}'
def __get__(self, obj, objtype=None):
if obj is None:
return self
return getattr(obj, self.private, None)
def __set__(self, obj, value):
if not isinstance(value, (int, float)):
raise TypeError(f'{self.name}은 숫자여야 합니다')
setattr(obj, self.private, value)
class Circle:
radius = TypedAttr()
def __init__(self, r):
self.radius = r # TypedAttr.__set__ 호출
c = Circle(5)
print(c.radius) # 5
c.radius = "big" # TypeError
property 내부 구현 원리
python
# property는 디스크립터로 구현됨
class MyProperty:
def __init__(self, fget=None, fset=None, fdel=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
def __get__(self, obj, objtype=None):
if obj is None:
return self
return self.fget(obj)
def __set__(self, obj, value):
if self.fset is None:
raise AttributeError("읽기 전용")
self.fset(obj, value)
def setter(self, fset):
return type(self)(self.fget, fset, self.fdel)
class Temperature:
@MyProperty
def celsius(self):
return self._c
@celsius.setter
def celsius(self, value):
self._c = value
set_name 활용 (Python 3.6+)
python
class Validator:
def __set_name__(self, owner, name):
self.attr = name # 자동으로 속성 이름 할당
def __set__(self, obj, value):
if value < 0:
raise ValueError(f'{self.attr}는 음수가 될 수 없습니다')
obj.__dict__[self.attr] = value
def __get__(self, obj, objtype=None):
return obj.__dict__.get(self.attr, 0) if obj else self
class Product:
price = Validator()
quantity = Validator()
관련 개념