Python tech/고급 파이썬 공부

Python class 속성 대신 프로퍼티를 사용해보자

콜레오네 2023. 8. 17. 00:23

해당 포스팅은 [클린코드, 이제는 파이썬이다] 저서의 일부입니다.


프로퍼티를 사용하면 객체의 속성을 읽거나 수정, 삭제할 때마다
몇몇 특정 코드를 실행해서 객체가 유효하지 않은 상태에 빠지지 않게 할 수 있다.
다른 언어에서는 흔히 getter, setter로 부르는 경우가 많다. 파이썬 또한 비슷하긴 하다.

프로퍼티 Property

속성 Attribute을 읽고 바꾸고 삭제하는 방법을 조절할 수 있도록 특별히 지정된
getter, setter, deleter 메서드를 가진 속성이다.

예를 들어,
정수값만 가져야하는 경우에 문자열을 할당한다면 버그가 발생해야 한다.
프로퍼티는 setter 메서드를 호출함으로써, 유효하지 않은 값 설정을 수정하거나
적어도 조기에 감지하는 코드를 실행한다.

아래는 일반 속성을 사용한 class이다.
someAttr 속성을 마음대로 설정하고 수정할 수 있다.

>>> class Blog:
...     def __init__(self, name):
...         self.someAttr = name
...
>>>
>>> obj = Blog('my blog')
>>> obj.someAttr
'my blog'
>>> obj.someAttr = 'my diary'
>>> obj.someAttr
'my diary'
>>> del obj.someAttr

하지만 위 코드는 someAttr이 무조건 문자열이어야 하지만, 다른 타입의 객체가 할당될 수 있다.

위 코드에 프로퍼티를 적용해보자
다음과 같은 순서를 따르면 된다.

  1. 접두사 언더바를 붙여라
  2. @property 데코레이터를 사용해서 someAttr이란 메서드를 생성해라. self 파라미터가 있어야 한다.
  3. @someAttr.setter 데코레이터를 붙여서 someAttr 메서드를 하나 더 만들어라. self, value 파라미터가 필요하다.
  4. @someAttr.deleter 데코레이터를 붙여서 또 만들어라. self 파라미터만 있으면 된다.

그러면 아래와 같아진다.

>>> class Blog:
...     def __init__(self):
...         self.someAttr = 'init title'

...     @property
...     def someAttr(self):  # getter method
...         return self._someAttr

...     @someAttr.setter
...     def someAttr(self, value):  # setter method
...         self._someAttr = value

...     @someAttr.deleter
...     def someAttr(self):  # deleter method
...         del self._someAttr
...
>>>
>>>
>>> obj = Blog()
>>> obj.someAttr
'init title'
>>> obj.someAttr = "My Blog"
>>> obj.someAttr
'My Blog'
>>> del obj.someAttr

출력은 동일하다. 하지만 내부 동작 과정이 다르다.
단, 클래스 밖 코드는 절대 _someAttr에 접근하지 않는다.

외부 코드는 대신 someAttr 프로퍼티에 접근한다.
getter, setter, deleter 메서드를 만들면서 someAttr이란 속성을 _someAttr로 이름 바꾸는데

우리는 이를 someAttr 프로퍼티라고 부른다.
그리고 _someAttr 속성은 지원필드 backing filed라고 부르며 프로퍼티의 기반이 된다.

접두사를 붙이지 않는다면
getter 함수 호출 시, 함수가 재귀호출되어 프로그램이 중단된다.
반드시 접두사를 붙여 재귀 호출을 방지해주어야 한다.

데이터 검증을 위한 setter

프로퍼티를 사용하는 일반적인 시나리오중 하나는
데이터의 유효성을 검증하기 위함이다.
어떤 속성이 내가 원하는 데이터 범위여야 하거나, 혹은 타입을 고정해야하는 경우 사용한다.
유효하지 않은 값이 들어오면, 그 즉시 예외를 발생시키기 때문에
개발 초기에 코드 오류를 발견할 수도 있다.

>>> class Blog:
...     def __init__(self):
...         self.someAttr = 10  # Interger만 가능하다

...     @property
...     def someAttr(self):  # getter method
...         return self._someAttr

...     @someAttr.setter
...     def someAttr(self, value):  # setter method
...         if not isinstance(value, int):  # if not interger type
...             raise Exception("You set wrong type")
...         self._someAttr = value

...     @someAttr.deleter
...     def someAttr(self):  # deleter method
...         del self._someAttr
...
>>>
>>>
>>> obj = Blog()
>>> obj.someAttr
10
>>> obj.someAttr = "My Blog"  # if set str type
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 10, in someAttr
Exception: You set wrong type

위와 같이 정수형만 세팅할 수 있지만, str 타입이 세팅된다면
그 즉시 raise를 통해 에러를 발생시키고 종료되는 것을 확인할 수 있다.

읽기 전용 property

setter와 deleter 메서드를 생략하면 프로퍼티를 읽기 전용으로 만들 수 있다.
이는 당연히 read에만 사용되는 속성에 한정해야 한다.

>>> class Blog:
...     def __init__(self):
...         self.someAttr = 10
...         self.someAttr2 = 11
...     @property
...     def total(self):
...         return (self.someAttr + self.someAttr2 * 2)
...
>>> obj = Blog()
>>> obj.total
32

위 코드에서 total 프로퍼티는 _total 이란 지원변수에 기반하지 않는다.
그저 someAttr과 someAttr2를 연산해서 보여줄 뿐이다.

읽기전용 프로퍼티를 변경하려한다면 프로그램이 중단되겠지만
변경하면 안되는 프로퍼티를 변경하게 냅두는 것보단 나은 방법일 것이다.

상수 변수와 혼동하지 말자
상수 변수는 대문자로 쓰여지며, 하드코딩되어 프로그래머가 건들이기 전까지는 변하지 않는다.
다만 읽기전용 프로퍼티는 다른 객체와 연관되어 있고 유기적으로 변할 수 있는 값이다.

반응형