728x90
Python 데코레이터의 핵심, wrapper 실습 가이드
이 글은 데코레이터 안에서 항상 등장하는 wrapper 함수에만 초점을 맞춰, 실습 중심으로 동작 원리를 이해하도록 돕는 가이드입니다.
1. wrapper의 정체는 무엇인가?
데코레이터의 기본 형태는 다음과 같습니다.
def deco(func):
def wrapper(*args, **kwargs):
# (1) 실행 전
result = func(*args, **kwargs) # 원래 함수 호출
# (2) 실행 후
return result
return wrapper
@deco
def hello():
print("Hello")
핵심은 다음 두 줄입니다.
@deco→hello = deco(hello)로 치환deco가 반환하는 것은wrapper이므로, 이제hello()는 곧wrapper()호출
즉, wrapper는 “원래 함수를 감싼 새로운 함수”이고,
그 안에서 원하는 전/후 처리 로직을 넣을 수 있습니다.
그 안에서 원하는 전/후 처리 로직을 넣을 수 있습니다.
2. wrapper 동작 원리 – 인자 없는 함수 예제
def deco(func):
def wrapper():
print("[wrapper] 함수 호출 전")
func()
print("[wrapper] 함수 호출 후")
return wrapper
@deco
def hello():
print("hello 함수 본문")
hello()
실행 흐름:
@deco→hello = deco(hello)hello()→ 내부적으로wrapper()실행wrapper안에서:- “함수 호출 전” 출력
func()로 원래hello실행- “함수 호출 후” 출력
3. 인자가 있는 함수에 wrapper 적용하기
두 숫자를 더하는 함수에 로그를 찍는 예제입니다.
def log_deco(func):
def wrapper(a, b):
print(f"{func.__name__} 호출: a={a}, b={b}")
result = func(a, b)
print(f"{func.__name__} 결과: {result}")
return result
return wrapper
@log_deco
def add(a, b):
return a + b
add(3, 5)
이 방식은 add에는 잘 동작하지만,
인자 개수가 다른 함수에는 적용할 수 없다는 한계가 있습니다.
4. 범용 wrapper를 위한 *args, **kwargs
여러 종류의 함수에 모두 쓰려면 다음과 같이 정의하는 것이 일반적입니다.
def log_deco(func):
def wrapper(*args, **kwargs):
print(f"{func.__name__} 호출, args={args}, kwargs={kwargs}")
result = func(*args, **kwargs)
print(f"{func.__name__} 결과: {result}")
return result
return wrapper
@log_deco
def add(a, b):
return a + b
@log_deco
def greet(name, msg="안녕"):
return f"{msg}, {name}"
add(1, 2)
greet("홍길동", msg="반가워")
이렇게 하면 인자 형태와 상관없이, 모든 함수에 같은 데코레이터를 적용할 수 있습니다.
5. wrapper 안에 “실행 전/후 훅” 넣기
5-1. 실행 시간 측정 데코레이터
import time
def timeit(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__} 실행 시간: {end - start:.4f}초")
return result
return wrapper
@timeit
def slow_add(a, b):
time.sleep(1)
return a + b
slow_add(3, 4)
5-2. 조건/권한 체크
def require_positive(func):
def wrapper(x):
if x <= 0:
print("양수만 허용됩니다.")
return None
return func(x)
return wrapper
@require_positive
def sqrt(x):
return x ** 0.5
sqrt(9) # 정상
sqrt(-1) # wrapper에서 걸러짐
6. @wraps와 wrapper – 메타데이터 보존
6-1. @wraps 없이
def deco(func):
def wrapper(*args, **kwargs):
print("실행 중")
return func(*args, **kwargs)
return wrapper
@deco
def hello():
"""인사하는 함수입니다."""
print("Hello")
print(hello.__name__) # wrapper
print(hello.__doc__) # None
6-2. @wraps 사용
from functools import wraps
def deco(func):
@wraps(func)
def wrapper(*args, **kwargs):
print("실행 중")
return func(*args, **kwargs)
return wrapper
@deco
def hello():
"""인사하는 함수입니다."""
print("Hello")
print(hello.__name__) # hello
print(hello.__doc__) # 인사하는 함수입니다.
@wraps(func)는 wrapper에게 원래 함수의 이름, docstring 등 메타데이터를 복사해 주어,
디버깅과 문서화 시에 함수 정보가 깨지지 않도록 해줍니다.
7. wrapper와 클로저(closure)
wrapper는 바깥 스코프의 변수를 기억하는 클로저이기도 합니다.
7-1. 단순 클로저 예제
def multiplier(n):
def wrapper(x):
return n * x
return wrapper
times2 = multiplier(2)
times3 = multiplier(3)
print(times2(10)) # 20
print(times3(10)) # 30
7-2. 데코레이터 + 클로저
def log_prefix(prefix):
def decorator(func):
def wrapper(*args, **kwargs):
print(prefix, "시작")
result = func(*args, **kwargs)
print(prefix, "끝")
return result
return wrapper
return decorator
@log_prefix("[TEST]")
def run():
print("실행!")
run()
여기서 wrapper는 외부 스코프의 prefix 값을 기억하고 있습니다.
이처럼 데코레이터는 클로저와 함께 사용되는 경우가 매우 많습니다.
8. 연습 문제
연습 1: 함수 호출 횟수 세기
요구 사항:
- 어떤 함수에 붙이면 그 함수가 몇 번 호출됐는지 출력
- 예: 첫 호출 → “호출 횟수: 1”, 두 번째 → “호출 횟수: 2”
힌트:
- 데코레이터 안에
count = 0두고, wrapper 안에서nonlocal count사용
연습 2: 간단 캐시 데코레이터
요구 사항:
- 동일한 인자로 호출된 함수는 이전 결과를 재사용
- 두 번째 호출부터는 계산 없이 바로 반환
힌트:
- 데코레이터 안에
cache = {}딕셔너리 두고,args를 키로 사용
연습 문제를 직접 구현해 보면,
“아, wrapper가 결국 함수 호출 전후에 공통 로직을 넣는 자리구나”라는 감각이 확실하게 자리 잡습니다.
“아, wrapper가 결국 함수 호출 전후에 공통 로직을 넣는 자리구나”라는 감각이 확실하게 자리 잡습니다.
9. 한줄 정리
- wrapper는 데코레이터가 반환하는 “새로운 함수”이다.
- 원래 함수는 사실상 wrapper로 대체되고, wrapper 안에서 원래 함수를 원하는 타이밍에 호출한다.
- 범용성을 위해 거의 항상
*args, **kwargs를 사용한다. - 공통 전/후 처리, 조건 체크, 로깅, 캐싱 등의 로직을 wrapper 안에 넣으면 깔끔하게 재사용할 수 있다.
'프로그래밍 > Python' 카테고리의 다른 글
| [Python] lru_cache는 언제 사용해? (0) | 2025.12.15 |
|---|---|
| [Python] @property는 언제 사용해? (0) | 2025.12.15 |
| [Python] classmethod는 언제 사용해? (0) | 2025.12.15 |
| [Python] staticmethod는 언제 사용해? (0) | 2025.12.15 |
| [Python] Decorator 입문 훑기 (1) | 2025.12.15 |