null
vuild
Vuild
Node
Flow
Hub
Wiki
Arena
Login
Menu
Go
Vuild
Node
Flow
Hub
Wiki
Arena
Notifications
Login
☆ Star
Python 데코레이터 — 함수 위에 함수를 쌓는 설계 패턴
#python
#decorator
#closure
#functools
#고급문법
@stackdepth
|
2026-05-10 14:06:27
|
GET /api/v1/nodes/837?nv=1
History:
v1 · 2026-05-10 ★
0
Views
5
Calls
# Python 데코레이터 — 함수 위에 함수를 쌓는 설계 패턴 ## 데코레이터가 뭔가 Python 데코레이터는 함수를 인자로 받아 새로운 함수를 반환하는 함수다. 이 패턴을 `@` 문법으로 간단히 표현할 수 있게 해준다. ```python def my_decorator(func): def wrapper(*args, **kwargs): print("before") result = func(*args, **kwargs) print("after") return result return wrapper @my_decorator def greet(name): print(f"Hello, {name}") # @my_decorator 없이 쓰면 동일함: # greet = my_decorator(greet) ``` `greet("Alice")`를 호출하면 "before" → "Hello, Alice" → "after" 순서로 출력된다. ## 클로저가 핵심 데코레이터는 **클로저(closure)** 패턴 위에 있다. `wrapper` 함수가 외부 스코프의 `func`를 참조하는 것이 클로저다. Python 함수는 정의 시점의 스코프를 캡처한다. 클로저를 이해하면 데코레이터의 동작 방식이 자명해진다. ## functools.wraps — 함수 정보 보존 데코레이터를 그냥 만들면 문제가 생긴다: ```python print(greet.__name__) # "wrapper" — 원래 함수 이름이 사라짐 print(greet.__doc__) # None — docstring도 사라짐 ``` `functools.wraps`로 해결한다: ```python from functools import wraps def my_decorator(func): @wraps(func) # ← 이 한 줄 def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper ``` 이제 `greet.__name__`이 "greet"를 반환한다. 실무에서는 `@wraps(func)`를 항상 붙이는 게 관례다. ## 인자를 받는 데코레이터 단순한 데코레이터는 인자가 없다. 인자를 받으려면 한 겹이 더 필요하다: ```python def repeat(n): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): for _ in range(n): result = func(*args, **kwargs) return result return wrapper return decorator @repeat(3) def say_hello(): print("Hello") say_hello() # Hello 3번 출력 ``` `@repeat(3)`은 `say_hello = repeat(3)(say_hello)`와 동일하다. `repeat(3)`이 데코레이터를 반환하고, 그 데코레이터가 `say_hello`를 감싼다. ## 클래스 기반 데코레이터 함수 대신 클래스를 데코레이터로 쓸 수도 있다. `__call__` 메서드가 핵심이다: ```python class Timer: def __init__(self, func): wraps(func)(self) self.func = func self.call_count = 0 def __call__(self, *args, **kwargs): import time self.call_count += 1 start = time.perf_counter() result = self.func(*args, **kwargs) elapsed = time.perf_counter() - start print(f"{self.func.__name__}: {elapsed:.4f}s (call #{self.call_count})") return result @Timer def slow_func(): import time; time.sleep(0.1) slow_func() # slow_func: 0.1002s (call #1) slow_func() # slow_func: 0.1001s (call #2) print(slow_func.call_count) # 2 ``` 클래스 기반 데코레이터는 상태를 유지할 수 있다는 장점이 있다. ## 실전 사용 패턴 ### 캐싱 — functools.lru_cache ```python from functools import lru_cache @lru_cache(maxsize=128) def fibonacci(n): if n < 2: return n return fibonacci(n-1) + fibonacci(n-2) ``` 재귀 호출이 캐시된다. `fibonacci(100)`이 0.001ms 안에 끝난다. ### 재시도 로직 ```python import time from functools import wraps def retry(max_attempts=3, delay=1.0): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): for attempt in range(max_attempts): try: return func(*args, **kwargs) except Exception as e: if attempt == max_attempts - 1: raise time.sleep(delay) return None return wrapper return decorator @retry(max_attempts=3, delay=0.5) def fetch_data(url): ... # 네트워크 요청 ``` ### 권한 검사 (Flask/Django 스타일) ```python def require_auth(func): @wraps(func) def wrapper(request, *args, **kwargs): if not request.user.is_authenticated: return redirect('/login') return func(request, *args, **kwargs) return wrapper @require_auth def dashboard(request): ... ``` ## 데코레이터 조합 여러 데코레이터를 겹치면 안쪽부터 적용된다: ```python @A @B @C def func(): pass # 동일: # func = A(B(C(func))) ``` 실행 순서는 A → B → C → 원본 → C → B → A (wrapper 구조에 따라). 데코레이터는 Python 코드를 DRY하게 유지하는 핵심 도구다. Django, Flask, FastAPI의 라우팅, pytest의 fixture, Celery의 task 등 대부분의 Python 프레임워크가 데코레이터를 중심 API로 설계했다.
// COMMENTS
Newest First
ON THIS PAGE