Python @lru_cache 실습 중심 가이드
이 글은 Python의 @lru_cache 데코레이터를 사용해 함수 결과를 캐싱하고, 성능을 개선하는 방법을 실습 위주로 설명합니다.
1. @lru_cache 한 줄 정의
@lru_cache는 “함수의 결과를 캐싱하여, 같은 인자로 다시 호출될 때 계산을 생략하고 저장된 값을 반환하는 데코레이터”입니다.
특히 재귀 함수나, 같은 입력으로 여러 번 호출되는 비싼 연산에서 큰 효과를 발휘합니다.
2. 기본 사용법
from functools import lru_cache
@lru_cache(maxsize=None)
def fib(n):
if n <= 1:
return n
return fib(n-1) + fib(n-2)
print(fib(10))
print(fib(10)) # 두 번째 호출은 캐시된 값 사용
maxsize=None으로 설정하면 캐시 크기에 제한이 없습니다(메모리가 허용하는 한).
재귀적인 피보나치 함수에 적용하면 성능이 눈에 띄게 좋아집니다.
3. 주요 옵션 – maxsize, typed
3-1. maxsize
maxsize는 캐시에 저장할 수 있는 최대 엔트리 개수입니다.
from functools import lru_cache
@lru_cache(maxsize=2)
def add(a, b):
print(f"실제 계산: {a} + {b}")
return a + b
add(1, 2) # 계산 발생
add(1, 2) # 캐시 사용
add(2, 3) # 계산 발생
add(3, 4) # 계산 발생 (오래 안 쓰인 항목부터 제거)
add(1, 2) # 상황에 따라 다시 계산 발생 가능
maxsize를 초과하면,
“가장 오랫동안 사용되지 않은(LRU, Least Recently Used)” 항목부터 제거됩니다.
3-2. typed
typed 옵션은 인자의 타입을 구분할지 여부를 결정합니다.
from functools import lru_cache
@lru_cache(maxsize=None, typed=False)
def show(x):
print(f"계산! x={x} ({type(x)})")
return x
show(1) # 계산
show(1.0) # typed=False 이면 캐시 사용 (동일 키)
@lru_cache(maxsize=None, typed=True)
def show_typed(x):
print(f"계산! x={x} ({type(x)})")
return x
show_typed(1) # 계산
show_typed(1.0) # typed=True 이면 별도 계산
4. 캐시 상태 확인 & 초기화
4-1. cache_info()
from functools import lru_cache
@lru_cache(maxsize=4)
def slow_square(n):
print(f"계산: {n} * {n}")
return n * n
slow_square(2)
slow_square(3)
slow_square(2)
print(slow_square.cache_info())
출력 예:
CacheInfo(hits=1, misses=2, maxsize=4, currsize=2)
- hits: 캐시를 사용한 횟수
- misses: 실제 계산이 수행된 횟수
- maxsize: 설정된 최대 캐시 크기
- currsize: 현재 캐시에 저장된 항목 수
4-2. cache_clear()
slow_square.cache_clear()
캐시를 초기화하여, 이후 호출부터는 다시 계산이 수행되도록 만들 수 있습니다.
5. 실전 예제 1 – 재귀 함수 최적화
5-1. 캐시 없이
import time
def fib(n):
if n <= 1:
return n
return fib(n-1) + fib(n-2)
start = time.time()
print(fib(35))
end = time.time()
print("걸린 시간:", end - start)
5-2. @lru_cache 사용
import time
from functools import lru_cache
@lru_cache(maxsize=None)
def fib_cached(n):
if n <= 1:
return n
return fib_cached(n-1) + fib_cached(n-2)
start = time.time()
print(fib_cached(35))
end = time.time()
print("걸린 시간(캐시):", end - start)
동일한 값을 반복 계산하지 않고 캐시를 활용하기 때문에, 큰 n에 대해서도 실행 시간이 크게 줄어듭니다.
6. 실전 예제 2 – 비싼 연산 캐싱
설정 정보를 읽는 함수에 캐시를 적용하는 예제입니다.
from functools import lru_cache
import time
@lru_cache(maxsize=1)
def load_config():
print("설정 로딩 중... (예: 파일/DB 읽기)")
time.sleep(1)
return {"env": "prod", "debug": False}
print(load_config()) # 첫 호출: 느림
print(load_config()) # 두 번째: 바로 반환
인자가 없는 함수라도 @lru_cache를 사용할 수 있으며,
“한 번 계산해두고 계속 재사용하면 좋은 값”에 특히 적합합니다.
7. @lru_cache 사용 시 주의할 점
7-1. 인자는 해시 가능(hashable)해야 한다
내부적으로는 “인자 → 결과”를 딕셔너리 형태로 저장하기 때문에, 키로 사용할 수 없는 타입(예: list, dict)을 인자로 사용할 수 없습니다.
from functools import lru_cache
@lru_cache(maxsize=None)
def f(x):
return x
f([1, 2, 3]) # TypeError: unhashable type: 'list'
대신 튜플 등 해시 가능한 타입을 사용해야 합니다.
f((1, 2, 3)) # OK
7-2. 부작용이 있는 함수에는 사용하지 않기
@lru_cache(maxsize=None)
def send_email(to):
print(f"{to}에게 이메일 전송!")
# 실제 이메일 전송 코드...
return "OK"
같은 인자로 두 번째 호출부터는 실제 전송이 일어나지 않고, 캐시된 결과만 반환됩니다. 즉, 외부 부작용(side effect)이 중요한 함수에는 @lru_cache를 사용하면 안 됩니다.
7-3. 메모리 사용량
maxsize=None으로 두고 다양한 인자가 계속 들어오면,
캐시가 무한히 커져 메모리를 많이 사용할 수 있습니다.
maxsize를 적절한 값으로 제한하는 것이 좋습니다. (예: maxsize=1024)
8. 다른 데코레이터와 함께 사용할 때
@lru_cache는 다른 데코레이터와 함께 사용할 수도 있습니다.
from functools import lru_cache, wraps
def debug(func):
@wraps(func)
def wrapper(*args, **kwargs):
print("호출:", func.__name__, args, kwargs)
return func(*args, **kwargs)
return wrapper
@debug
@lru_cache(maxsize=None)
def square(n):
print("실제 계산:", n)
return n * n
데코레이터의 순서에 따라 로그가 찍히는 위치나 캐싱 동작 방식이 달라질 수 있으므로, 필요에 따라 순서를 조정해야 합니다.
9. 한 줄 요약
- @lru_cache는 “함수의 결과를 캐싱하는 데코레이터”이다.
- 중복 계산이 많은 재귀/비싼 연산에 사용하면 큰 성능 향상이 있다.
maxsize,typed옵션과cache_info(),cache_clear()메서드를 잘 활용하면 편리하다.- 인자는 해시 가능해야 하며, 부작용이 중요한 함수에는 사용하지 않는 것이 좋다.
10. 연습 문제
연습 1 – factorial 캐싱
factorial(n)을 재귀로 구현하고, @lru_cache를 적용해 보세요.
n=1000까지 계산- 마지막에
factorial.cache_info()출력
연습 2 – API 캐싱 시뮬레이션
다음과 같은 함수를 작성해 보세요.
import time
def get_user_from_server(user_id):
time.sleep(1)
return {"id": user_id, "name": f"user{user_id}"}
여기에 @lru_cache(maxsize=128)를 적용하고,
같은 user_id로 여러 번 호출하면서 캐시 효과를 체감해 보세요.
'프로그래밍 > Python' 카테고리의 다른 글
| [Python] @property는 언제 사용해? (0) | 2025.12.15 |
|---|---|
| [Python] classmethod는 언제 사용해? (0) | 2025.12.15 |
| [Python] staticmethod는 언제 사용해? (0) | 2025.12.15 |
| [Python] decorator에서 wrapper 자세히 보기 (0) | 2025.12.15 |
| [Python] Decorator 입문 훑기 (1) | 2025.12.15 |