youngfromnowhere

[Python] Decorator3. Property 본문

Python

[Python] Decorator3. Property

곽일땡 2022. 11. 16. 01:18

Python에는 다른 OOP 언어에서 제공하는 '접근제어자'가 존재하지 않는다. 단지 instance variable 앞에 _를 붙여서

해당 변수에 요구되는 access mode를 표시하는 convention만 존재할 뿐이다.

 

다만 property라는 built-in function (built-in class)을 사용하면 instance variable에 대한 접근을 제어할 수 있다. Python의 property에 대해 검색하면 property함수의 사용법을 설명한 글은 많이 나온다. 여기서는 공식문서에서 볼 수 있는 Pure python equivalents (똑같이 동작하도록 하는 python만으로 이루어진 코드)를 보면서 내부적인 작동 원리를 더 이해해보자.

 

https://docs.python.org/3/howto/descriptor.html#properties

 

Descriptor HowTo Guide — Python 3.11.0 documentation

Author Raymond Hettinger Contact Descriptors let objects customize attribute lookup, storage, and deletion. This guide has four major sections: The “primer” gives a basic overview, moving gently from simple examples, adding one feature at a time. Start

docs.python.org

 

property()의 내부를 python으로 구현한다면 다음과 같다.

class property(object):

    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("Unreadible attribute")
        return self.fget(obj)

    def __set__(self, obj, value):
        if self.fset is None:
            raise AttributeError("Can't set attribute")
        self.fset(obj, value)

    def __delete__(self, obj):
        if self.fdel is None:
            raise AttributeError("Can't delete attribute")
        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__)

이제 다음과 같이 instance variable "_x"를 갖는 class C를 정의할 때, class variable로 property instance를 정의해주자.

class C:
    def __init__(self):
        self._x = "Value"

    def getx(self):
        return self._x

    def setx(self, value):
        self._x = value

    def delx(self):
        del self._x

    x = property(getx, setx, delx, "I'm the 'x' property")

이렇게 하면 C 내부의 getx, setx, delx method들은 property class의 instance인 x 내부의

x.fget, x.fset, x.fdel로 전달된다.

c = C()로 C의 instance c를 생성하면,

이제 c._x를 직접 호출하는대신 c.x를 통해 c._x를 호출하거나 수정하거나 삭제할 수 있다.

즉 C 클래스를 import하여 사용하는 '사용자'는
그냥 C의 instance c 내부의 변수 x를 조작한다고 생각하지만 사실은

x라는 property instance를 통해 간접적으로 C 내부의 method들을 호출하는 것이다.

 

이 때 사용자가 실제 instance variable의 이름이 아닌 property instance의 이름인 'x'만 알게 하고,

x 내부에 setx, delx등을 정의하지 않으면

_x는 외부로부터 쓰기나 지우기가 불가능한 읽기전용의 instance variable이 되는 것이다.

 

내부적인 호출과정을 좀 더 알아보면,

c.x 호출시,
c.__getattribute__(x)가 호출되어
c.x를 다음과 같이 변환한다.
type(c).__dict__['x'].__get__(c,type(c))
이는 다음의 call과 equivalent하다.
C.x.__get__(c,C)
C.x.fget(C)
C.getx(c)
c.getx()
c.getx() returns c._x

c.x = "New Value",
c.__setattribute__(x)가 호출되어
c.x를 다음과 같이 변환한다.
type(c).__dict__['x'].__set__(c,"New Value")
이는 다음과 equivalent하다.
C.x.__set__(c,"New Value")
C.x.fset(c, "New Value")
C.setx(c, "New Value")
c._x = "New Value"

del c.x equivalents to
type(c).__dict__['x'].__delete__(c,type(c))
C.x.__delete__(c,C)
C.x.fdel(c)
C.delx(c)
del c._x

이 과정을 보면 python interpreter가 중간에 __set__, __get__, __delete__라는 method들을 호출하는걸 볼 수 있는데 이들을 "descriptor protocol"이라 한다. python interpreter가 변수 호출, 변수 저장, object 삭제 등의 명령을 만날때 호출하는 method들이다. property class의 구현내용은 이 descriptor protocol을 override한 것이다.

 

이제 Decorator를 이용하여 classC를 정의한 코드를 간결하게 만들어보자.

class C:
    def __init__(self):
        self._x = "Value"

    @property
    def x(self):
        "I'm the 'x' property."
        return self._x

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

    @x.deleter
    def x(self):
        del self._x

@property 부분 : x = property(x). 즉, self._x를 반환하는 method x를 property class에 fget으로 입력하고, 그 instance를 변수 x에 다시 저장.

@x.setter 부분 : x는 property class의 instance이다. 즉, x 내부의 x.setter method에, self._x = value 를 실행하는 method x를 fset으로 전달한다. (@x.deleter 부분도 똑같이 이해할 수 있다.)

 

결론 :

property class를 이용하여 instance variable에 대한 접근을 제어한다는 것은, target이 되는 class 내부에 property의 instance를 class variable로 저장하고, 그 instance를 통해 간접적으로 제어한다는 것이다. property class 내부에는 descriptor protocol이 override되어 있어서 실제 instance variable에 접근하는 method들을 반환한다. 이렇게 하여 property instance 'x'가 instance variable '_x'를 대신한다.

이 때 x에 deleter를 전달하지 않거나 setter를 전달하지 않고, C class 를 사용할 사용자에게 instance variable의 이름을 'x'라고만 알리면, _x에 대한 접근이 제한되는 것이다.

 

 


@property의 또 다른 활용 (접근제어 용도 외)

읽기전용 객체 변수를 정의할 때, 그 변수가 다른 객체 변수들로부터 유추될 수 있는 경우, 실제로 객체 변수를 정의하지 않고 다음과 같이 구현할 수 있다. (출처 https://www.daleseo.com/python-property/)

class Person:
    def __init__(self, first_name, last_name, age):
        self.first_name = first_name
        self.last_name = last_name
        self.age = age

    @property
    def full_name(self):
        return self.first_name + " " + self.last_name

'Python' 카테고리의 다른 글

[Python] pyenv로 가상환경 구축하기.  (0) 2023.09.19
[Python] python의 special methods  (0) 2022.11.17
[Python] Decorator 2. abstractmethod()  (0) 2022.11.15
[Python] Decorator  (0) 2022.11.07
[Python] 가상환경  (0) 2022.11.03