youngfromnowhere
[Python] Decorator3. Property 본문
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 |