4-4. Descriptor(1)

지난 시간에 배웠던 메타 클래스가 난이도 최상이었으니,
descriptor는 쉽게 느껴질거라는 강사님의 말씀,, 사실이었다. 🥺

예전에 property 배울 때 getter, setter를 배웠다.
이 내용의 확장판이라고 생각하면 되니 복습을 하고오자! (link🔗)

getter, setter가 high level에서 작동한다면, descriptor는 low level에서 작동한다.

descriptor를 이용해서 우리가 원하는 방향으로 개입해서 프로그래밍을 할 수 있도록 해보자

Descriptor

  • 오늘의 키워드
    • Descriptor
    • get, set, del, Property

Descriptor가 뭔가요? 🤔

1. 객체에서 서로 다른 객체를 속성값으로 가질 때 사용된다.

다른 객체의 속성으로써 존재할 때, 그 속성에 대하여 읽기, 쓰기, 삭제 등의 동작을 할때, 동작에 따라 각 구현된 method가 호출되는 객체이다.

2. read, write, delete 등을 미리 정의 가능하다.

3. ⭐️ 두가지 종류로 나뉜다.

  • data descriptor (set, del) : 데이터를 직접 쓰거나, 지우는 수정을 한다.
  • non-data descriptor (get) : 데이터를 읽기만 한다.

장점

  • 읽기 전용 개체 생성을 할 수 있다.
  • 클래스를 의도하는 방향으로 생성 가능하다.

기본적인 descriptor 예제

  • 객체 내부 속성에 대해 read를 할 __get__, write를 할 __set__, delete를 할 __delete__
    세가지의 내부 메소드를 재정의할 수 있다.

name 이라는 속성에 대해서 접근하는 코드를 작성해보았다.

class descriptorEx1(object):
    def __init__(self, name="Default"):
        self.name = name

    def __get__(self, obj, objtype):
        return 'Get Method called -> self : {}, obj : {}, objtype: {}, name: {}'.format(
            self, obj, objtype, self.name)

    def __set__(self, obj, name):
        print('set method called')
        if isinstance(name, str):
            self.name = name
        else:
            # 조건에 따라 에러를 발생시킬 수 있다.
            raise TypeError('Name should be string')

    def __delete__(self, obj):
        print('Delete method called.')
        self.name = None
class Sample1(object):
    name = descriptorEx1()


s1 = Sample1()

# __set__ 호출
s1.name = 'Descriptor test1'
# set 메소드에서 알아서 문자열인지 체크하고,
# name을 __init__에서 할당을 해주는데, 
# 실제론 Sample1 class 내부의 name에 할당이 된다.

s1.name(10)
# 예외 발생 ; TypeError: 'str' object is not callable

# [atrr 확인]
# __get__ 호출
print('ex 1>', s1.name)
# ex 1> Get Method called
# -> self : <__main__.descriptorEx1 object at 0x7f9576ac8fa0>,
# obj : <__main__.Sample1 object at 0x7f9576aa0820>,
# objtype: <class '__main__.Sample1'>,
# name: Descriptor test1

# __ delte__ 호출
del s1.name

Sample1name 변수가 수정될 때 descriptorEx1 내부의
__set__, __get__ 등이 알아서 호출된다.
⭐️ 기존 getter, setter는 변수에 따라서 개수를 늘려줘야 했지만
위와 같이 descriptor 처리를 함으로서 코드의 양이 줄어들게 된다.


Property를 이용해 구현한 descriptor 예제

위의 예제는 Property는 Property 클래스 별도로, 사용은 사용 클래스 별도로 호출을 했다.

이번엔 Property를 이용해서 알아서 범용적인 역할을 하게 구현을 해보자.

Property

class property(fget=None, fset=None, fdel=None, doc=None)

  • 인자의 모든 디폴트값은 None이다. 없어도 그만이라는 뜻이다.
  • 메소드를 만들어서 인자로 넣어주면 된다.
    __get__, __set__과 같이 따로 정해진 규칙이 없으므로 마음대로 네이밍 해주자.
class DescriptorEx2(object):
    def __init__(self, value):
        self._name = value

    def getVal(self):
        return 'Get method called -> self:{}, name: {}'.format(self, self._name)

    def setVal(self, value):
        print('set method called')
        if isinstance(value, str):
            self._name = value
        else:
            raise TypeError('name should ba string.')

    def delVal(self):
        print('delete method called')
        self._name = None

    # property 메소드를 호출해서 별도로 지정한 get,set,del,doc를 넣어서 만들기
    name = property(getVal, setVal, delVal,
                    'Property 테스트를 하는 name 필드입니다.')


s2 = DescriptorEx2('Descriptor Test2')

위의 예제와 동일하게 동작한다.


[출처]

  1. 인프런 - 모두를 위한 파이썬 : 필수 문법 배우기 Feat. 오픈소스 패키지 배포 (Inflearn Original)

  2. Python, descriptor에 대하여

좋은 웹페이지 즐겨찾기