외부효과(side effect)가 없고 특정한 인풋에 대해 고정된 아웃풋이 나오는 함수를 순수함수(pure function)이라고 합니다.
이런 함수는 특정 인풋에 대한 아웃풋을 캐싱해두면, 같은 인풋이 들어왔을 때 다시 계산할 필요 없이, 캐싱된 아웃풋을 리턴하면 됩니다.
이미 정의된 순수 함수를 손쉽게 캐싱을 할 수 있게 해주는 데코레이터들이 functools 모듈에 있습니다.
cache, cached_property, lru_cache가 바로 그것 들입니다.
데코레이터이기 때문에 함수 정의 바로 위에 @cache, @cached_property, @lru_cache 를 써두는 것만으로 함수가 결과값들을 캐싱할 수 있게 해줍니다.
1. cache
파이썬 3.9 부터 사용 가능
특정 인풋에 대한 함수의 결과값을 저장합니다.(memoize)
from functools import cache
@cache
def my_function(n):
print(n)
def main():
my_function(2) # 2
my_function(3) # 3
my_function(4) # 4
my_function(3) # 출력되지 않음
my_function(4) # 출력되지 않음
if __name__ == '__main__':
main()
my_function 함수를 cache로 데코레이팅 한 결과, main 함수에서의 my_function 호출이 각 인풋에 대해 캐싱되므로, 매개변수 3, 4에 대한 호출이 다시 일어났을 때 내부 코드가 실행되지 않습니다.
2. cached_property
파이썬 3.8부터 사용 가능
property() 데코레이터에 캐싱 기능이 추가된 것입니다.
property() 처럼 클래스의 메소드를 프로퍼티화 합니다.
기존의 property()는 프로퍼티 메소드가 실행될 때마다 매번 계산해야 하지만,
cached_property()는 한번만 계산하고, 결과를 저장한 후 다시 실행될 때 저장된 결과를 리턴합니다.
기존의 property()에 대한 프로퍼티 메소드는 write 연산이 불가능하지만,
cached_property()는 write 연산이 가능합니다.
from functools import cached_property
class Student:
def __init__(self, name, age):
self._name = name
self._age = age
@cached_property
def name(self):
print('get name')
return self._name
@property
def age(self):
print('get age')
return self._age
def main():
st = Student("stlee", 32)
st.name # get name, 단 한번만 메소드 실행
st.name # 출력 없음
st.age # get age
st.age # get age, 프로퍼티에 접근할 때마다 메소드 실행
st.name = "Seungtae" # 새로운 값 할당 가능
# st.age = 33, AttributeError: can't set attribute 'age'
print(st.name) # Seungtae
if __name__ == '__main__':
main()
cached_property는 연산한 값을 객체의 __dict__에 저장하기 때문에 __slots__로 프로퍼티를 지정하는 경우는 작동하지 않습니다.
from functools import cached_property
class Student:
def __init__(self, name, age):
self._name = name
self._age = age
@cached_property
def name(self):
return self._name
@property
def age(self):
return self._age
def main():
st = Student("stlee", 32)
for k, v in st.__dict__.items():
print('key', k, 'value', v)
# 프로퍼티 호출 전
# key _name value stlee
# key _age value 32
st.name
for k, v in st.__dict__.items():
print('key', k, 'value', v)
# 프로퍼티 새로운 값 할당 전
# key _name value stlee
# key _age value 32
# key name value stlee, name 키에 stlee값이 생겼다.
st.name = "Seungtae"
for k, v in st.__dict__.items():
print('key', k, 'value', v)
# 프로퍼티 새로운 값 할당 후
# key _name value stlee
# key _age value 32
# key name value Seungtae, name 키에 새로운 값 Seungtae가 할당됨.
if __name__ == '__main__':
main()
3. lru_cache
lru_cache는 위의 cache와 비슷한 기능을 하지만, maxsize와 typed 파라미터를 받습니다.
lru_cache는 값을 dictionary에 저장하기 때문에, 키로 이용되는 파라미터가 hashable 해야합니다.
- maxsize : 캐시의 최대 크기, 기본값 128, None으로 지정되면 크기에 제한이 없다.(cache와 같음)
- typed : 파라미터가 같은 값으로 hash 되더라도 타입별로 구분할것인지 여부, 기본값 False
lru는 Least Recently Used의 약자로 가장 최근에 사용된 파라미터를 최대 maxsize까지 캐싱한다는 의미입니다.
lru_cache는 함수에 3개의 메소드를 추가합니다.
- cache_parameters : lru_cache를 호출할 때 주어진 파라미터 maxsize, typed값
- cache_info : 현재까지의 캐시 hits, misses, maxsize, cursize를 named tuple로 리턴
- cache_clear : 캐시 비우기
from functools import lru_cache
@lru_cache
def fib(n):
if n <= 1:
return n
return fib(n-1) + fib(n-2)
def main():
fib(10)
print(fib.cache_parameters()) # {'maxsize': 128, 'typed': False}, 기본값
print(fib.cache_info()) # CacheInfo(hits=8, misses=11, maxsize=128, currsize=11)
fib.cache_clear() # 캐시 비우기
print(fib.cache_info()) # CacheInfo(hits=0, misses=0, maxsize=128, currsize=0)
if __name__ == '__main__':
main()
typed가 기본적으로 False이기 때문에, 파라미터의 타입이 다르더라도 같은 값으로 hash된다면 같은 키로 간주됩니다.
from functools import lru_cache
from decimal import Decimal
from fractions import Fraction
@lru_cache
def showType(obj):
return type(obj)
def main():
t = showType(Decimal(10))
print(t) # <class 'decimal.Decimal'>
t = showType(Fraction(10))
print(t) # <class 'decimal.Decimal'>
if __name__ == '__main__':
main()
Decimal(10)과 Fraction(10)의 해쉬값이 같기 때문에, 같은 키로 인식되어 Fraction(10)을 넣었을 때도 Decimal(10)의 값이 나옵니다.
typed를 True로 놓게 되면, 키를 구분할때 타입도 보기 때문에, 따로 저장 됩니다.
from functools import lru_cache
from decimal import Decimal
from fractions import Fraction
@lru_cache(typed=True)
def showType(obj):
return type(obj)
def main():
t = showType(Decimal(10))
print(t) # <class 'decimal.Decimal'>
t = showType(Fraction(10))
print(t) # <class 'fractions.Fraction'>
if __name__ == '__main__':
main()
'프로그래밍언어 > 파이썬' 카테고리의 다른 글
[functools] cmp_to_key - old-style comparison function (0) | 2022.07.15 |
---|---|
[파이썬]itertools - zip, zip_longest 비교 (0) | 2022.07.13 |
[파이썬]itertools - product, permutations, combinations (0) | 2022.07.13 |
[파이썬]itertools - count, cycle, repeat (0) | 2022.07.13 |