Intro
안녕하세요, 오늘은 decorator에 대한 마지막 정리 글입니다. 앞선 글들을 통해 우리는 decorator란 무엇인지, 어떤 기능을 하는지, 그리고 @property, @staticmethod, @classmethod
와 같은 기본 decorator들에 대해서 알게되었습니다.
이번 글은 decorator 정리에 대한 마지막 글로, 제가 프로그래밍하면서 마주쳤던 그 외 decorator인 dataclass와 functools에 대해서 정리하겠습니다.
@dataclass
@dataclass
는 class를 선언할 때 class위에 붙여주는 decorator입니다. 이 decorator가 붙은 class는 dataclass가 되고, dataclass의 instance는 아래의 기능들을 수행할 수 있게 됩니다.
@dataclass
를 사용하기 위해서는 dataclass
함수를 import 해줘야 합니다.
from dataclass import dataclass
- intance를 출력하면 갖고 있는 필드 값 나열 (repr)
- hashable해서 dict나 set에 추가 가능
- 대소관계 비교
- 필드 값 변경 방지
위의 기능을 무조건 구현하는 것은 아니고, @dataclass
decorator의 인수를 조절해서 원하는 기능만 추가할 수 있습니다.
인수의 종류 및 기본 값은 아래와 같습니다.
@dataclasses.dataclass(*, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False)
- init:
__init__
이 구현되어 있지 않은 경우에 instance를 생성할 때 받은 값들을 자동으로 instance의 필드 값으로 매핑해주는__init__
함수를 추가해줍니다. - repr:
__repr__
을 추가해줍니다. 이를 추가하면 instance를 print했을 때, instance가 갖고있는 필드 값들을 알 수 있습니다. - eq:
__eq__
를 추가해줍니다. 이를 추가하면 instance가 갖고있는 필드 값들이 모두 동일하면 instance 비교 연산 (==
)에서True
를 반환합니다. - order:
__lt__, __le__, __gt__, __ge__
을 추가해줍니다. 이를 추가하면 instance사이의 필드 값에 기반한 대소관계 비교가 가능해집니다. - unsafe_hash:
__hash__
를 추가해줍니다. instance를 hashable하게 만들어줘서 dict나 set의 key로 사용될 수 있습니다. - frozen: instance의 필드를 불변하게 만듭니다.
이렇듯, @dataclass
는 우리가 원하는 기능을 구현하기 위해서 번거롭게 반복해야 하는 단순한 로직들을, 손쉽게 추가할 수 있도록 돕습니다.
주의) 넘겨주는 인자의 조합, 또는 기존 class에 추가하고자 하는 기능을 담당하는 매직 메소드 (
__~~~__
)가 이미 추가되어있을 시, 에러가 발생할 수 있습니다. 이에 대해서는 python의 dataclass구현 코드에 적힌 doc string을 참고하셔서 미리 예방하는 것을 추천드립니다.
@dataclass
와 관련된 code test는 이미 너무 잘 정리된 블로그 글을 발견해서, 해당 URL을 남기는 것으로 대체하도록 하겠습니다.
[파이썬] 데이터 클래스 사용법 (dataclasses 모듈)
functools
functools
는 함수에 여러 기능을 부여해줄 수 있는 모듈입니다. 오늘은 functools
가 제공해주는 여러 기능 중 cached, cached_property
에 대해서 알아보겠습니다.
@cache
decorator는 함수가 이전에 받았던 인수에 대한 결과값을 caching하는 것 입니다. 메모리는 좀 소모되겠지만, 중복 연산을 피할 수 있습니다.
피보나치 함수는 잘못 구현하면 내부에서 무수히 많은 recursive call이 발생해서 결과 값을 얻지 못할 수 있습니다. 그래서 이전에는 중복 연산이 발생하지 않도록 임의로 로직을 구현했지만, @cache
를 사용하면 쉽게 해결할 수 있습니다.
아래는 예시입니다.
from functools import cache
import time
@cache
def fibonacci_cached(n: int) -> int:
if n in [0, 1]:
return 1
else:
return fibonacci_cached(n-1) + fibonacci_cached(n-2)
def fibonacci_uncached(n: int) -> int:
if n in [0, 1]:
return 1
else:
return fibonacci_uncached(n-1) + fibonacci_uncached(n-2)
num = 40
print(f"start fibonacci{num} w/ cache")
start = time.time()
print(fibonacci_cached(40))
end = time.time()
print(f"result: {end - start} sec\n")
print(f"start fibonacci{num} w/o cache")
start = time.time()
print(fibonacci_uncached(40))
end = time.time()
print(f"result: {end - start} sec")
결과는 아래와 같습니다. @cache
가 붙은 fibonacci_cached()
의 실행 결과가 훨씬 빠름을 알 수 있습니다.
start fibonacci40 w/ cache
165580141
result: 0.0 sec
start fibonacci40 w/o cache
165580141
result: 48.55391979217529 sec
@cached_property
decorator는 @cache
와 달리 이전의 결과 값들을 모두 저장하지 않습니다. @cached_property
를 통해 caching 되는 것은 @cached_property
가 붙은 함수의 이름과 결과값이, instance의 새로운 필드와 필드 값으로 caching됩니다. 그리고 역시 호출할 때 마다 새롭게 실행되지 않고, caching된 값을 반환합니다.
아래는 예시입니다.
from functools import cached_property
import time
import numpy as np
class DataSet:
def __init__(self, sequence_of_numbers: np.ndarray):
self._data = tuple(sequence_of_numbers)
@property
def stdev_property(self) -> np.float64:
return np.std(self._data)
@cached_property
def stdev_cached_property(self):
return np.std(self._data)
dataset = DataSet(np.random.rand(1000))
print('Before stdev_property')
print(dataset.__dict__.keys())
print('\nAfter stdev_property')
dataset.stdev_property
print(dataset.__dict__.keys())
print('---------------------------------------')
print('\nBefore stdev_cached_property')
print(dataset.__dict__.keys())
print('\nAfter stdev_cached_property')
dataset.stdev_cached_property
print(dataset.__dict__.keys())
print('---------------------------------------')
print('\nstdev_cached_property value before _data changed')
print(dataset.stdev_cached_property)
dataset._data = np.random.rand(100)
print('\nstdev_cached_property value After _data changed')
print(dataset.stdev_cached_property)
결과는 아래와 같습니다. @cached_property
가 붙은 stdev_cached_property()
의 실행 결과 instance의 새로운 필드로 추가된고, 이후에 호출하면 재 실행없이 caching된 값이 반환되는 것을 볼 수 있습니다.
Before stdev_property
dict_keys(['_data'])
After stdev_property
dict_keys(['_data'])
---------------------------------------
Before stdev_cached_property
dict_keys(['_data'])
After stdev_cached_property
dict_keys(['_data', 'stdev_cached_property'])
---------------------------------------
stdev_cached_property value before _data changed
0.289598527937108
stdev_cached_property value After _data changed
0.289598527937108
마무리
4개의 글에 거쳐서 decorator에 대해서 정리해봤습니다! 모든 decorator를 다루진 못했지만, decorator의 메카니즘에 대해서 이해할 수 있었던 계기였던 거 같습니다.
감사합니다.
References
Python 3.9 dataclass doc string
Python 3.9 - dataclass documentation
[파이썬] 데이터 클래스 사용법 (dataclasses 모듈)
'[Python]' 카테고리의 다른 글
[Python] timeit 모듈과 pandas함수들의 속도 비교 테스트 (0) | 2021.07.18 |
---|---|
[Python] Mixin 이란? (0) | 2021.07.10 |
[Python] Decorator(3) - @classmethod, @staticmethod (0) | 2021.06.28 |
[Python] Decorator (2) - @property (0) | 2021.06.20 |
[Python] Decorator(1) - decorator란? (0) | 2021.06.15 |