Descriptor는 __get__, __set__ 혹은 __delete__ 중 적어도 한 개 이상을 구현하고 있는 class와 그 instance를 뜻한다. 좁은 의미로는 __get__을 구현하는 class와 instance를 뜻한다. 특히, __get__을 구현하고 __set__이나 __delete__를 구현하는 descriptor를 data descriptor라고 한다.

CPython에서 살펴보면 tp_descr_gettp_descr_set을 모두 구현하면 data descriptor, tp_descr_get만 구현하면 그냥 descriptor.

1
2
3
__get__(self, obj, objtype=None)
__set__(self, obj, value)
__delete__(self, obj)

Descriptor는 instance의 attribute에 접근할 때 찾은 object가 class variable인 descriptor이면 대응하는 slot을 호출한 결과를 return한다.

Usage Example

Dynamic Lookup

동적으로 값이 계속 바뀌는 attribute를 만들고 싶을 때 사용할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class AttributeCount:
    def __get__(self, obj, objtype=None):
        return len(obj.__dict__)
    
    def __set__(self, obj, value):
        raise AttributeError

class MyClass:
    num_attr = AttributeCount()

a = MyClass()

>>> a.num_attr
0
>>> a.x = 3
>>> a.num_attr
1

line 15에서 x라는 attribute가 새로 생겨서 a__dict__에 추가되었다. 그래서 그 다음 줄에서 num_attr 값이 커졌다.

Validator

__set__에서 유효성 검사 logic을 넣어줄 수 있다.

Built-in Decorators

Property Decorator

property object는 descriptor이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Foo:
    def __init__(self, x):
        self._x = x
    
    @property
    def x(self):
        return self._x

    @x.setter
    def x(self, x):
        self._x = x

a = Foo(3)

>>> a.x
3
>>> Foo.x
<property object at 0x7ff0981bed60>

이런 클래스가 있을 때, @property가 x를 decorating하므로 Foo.x는 기존 함수 Foo.x(self)를 wrapping하는 property type의 object가 되었다. 그리고 아래 코드는 property class를 python code로 표현해본 것이다. (실제로는 C로 구현되어 있다.)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class property:
    def __init__(self, fget=None, fset=None, fdel=None, doc=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
        if doc is None and fget is not None:
            doc = fget.__doc__
        self.__doc__ = doc
    
    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        if self.fget is None:
            raise AttributeError
        return self.fget(obj)

    def __set__(self, obj, value):
        if self.fset is None:
            raise AttributeError
        self.fset(obj, value)
    
    def __delete__(self, obj):
        if self.fdel is None:
            raise AttributeError
        self.fdel(obj)
    
    def getter(self, fget):
        return type(self)(fget, self.fset, self.fdel, self.__doc__)
    
    def setter(self, fset):
        return type(self)(self.fget, fset, self.fdel, self.__doc__)
    
    def deleter(self, fdel):
        return type(self)(self.fget, self.fset, fdel, self.__doc__)

Function

놀랍게도 파이썬 함수는 decorator이다. 왜냐? method를 return하기 때문. class variable로 정의된 함수 hello를 instance를 통해 접근하려고 하면, hello는 decorator이므로 hello.__get__()의 결과인 method를 return한다.


Back