Python Decorator 파라미터 전달 기법
Python decorator에 파라미터를 전달하는 다양한 방법과 실전 활용 예제를 알아봅니다.
Python Decorator 파라미터 전달 기법
Python decorator는 함수나 클래스의 동작을 수정하는 강력한 도구입니다. decorator에 파라미터를 전달하는 방법을 이해하면 더 유연한 코드를 작성할 수 있습니다.
Environment
$ python --version
Python 3.11.5
$ python -c "import functools; print(functools.__name__)"
functoolsProblem: decorator에 파라미터 전달 실패
def retry(func):
def wrapper(*args, **kwargs):
print(f"Retrying {func.__name__}")
return func(*args, **kwargs)
return wrapper
# This works
@retry
def my_function():
pass
# But this doesn't work as expected
@retry(max_retries=3)
def another_function():
pass
# TypeError: retry() got an unexpected keyword argument 'max_retries'Analysis: decorator 동작 원리
# 기본 decorator 구조
def simple_decorator(func):
def wrapper(*args, **kwargs):
# Before function call
result = func(*args, **kwargs)
# After function call
return result
return wrapper
# decorator with arguments - needs 3 levels of nesting
def decorator_with_args(arg1, arg2):
def actual_decorator(func):
def wrapper(*args, **kwargs):
print(f"Args: {arg1}, {arg2}")
return func(*args, **kwargs)
return wrapper
return actual_decoratorSolution: 다양한 decorator 파라미터 전달 방법
방법 1: 기본 decorator with arguments
import functools
import time
def retry(max_retries=3, delay=1):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
last_exception = None
for attempt in range(max_retries):
try:
return func(*args, **kwargs)
except Exception as e:
last_exception = e
if attempt < max_retries - 1:
print(f"Attempt {attempt + 1} failed: {e}")
time.sleep(delay)
raise last_exception
return wrapper
return decorator
# Usage
@retry(max_retries=5, delay=2)
def unreliable_function():
import random
if random.random() < 0.7:
raise ValueError("Random failure")
return "Success!"
result = unreliable_function()방법 2: class-based decorator
import functools
class CountCalls:
def __init__(self, func):
functools.update_wrapper(self, func)
self.func = func
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
print(f"Call {self.count} to {self.func.__name__}")
return self.func(*args, **kwargs)
def reset_count(self):
self.count = 0
@CountCalls
def say_hello():
print("Hello!")
say_hello() # Call 1 to say_hello
say_hello() # Call 2 to say_hello
say_hello.reset_count()방법 3: decorator factory with optional args
import functools
def log(level="INFO", include_args=True):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
if include_args:
args_str = str(args) if args else ""
kwargs_str = str(kwargs) if kwargs else ""
print(f"[{level}] {func.__name__}({args_str}, {kwargs_str})")
else:
print(f"[{level}] {func.__name__}")
return func(*args, **kwargs)
return wrapper
# Support both @log and @log() syntax
if callable(level):
func = level
level = "INFO"
return decorator(func)
return decorator
# Usage - both work
@log
def func1():
pass
@log()
def func2():
pass
@log(level="DEBUG", include_args=False)
def func3(x, y):
return x + y방법 4: decorator with class parameters
import functools
from typing import Callable, Any
class RateLimiter:
def __init__(self, calls_per_second: float):
self.calls_per_second = calls_per_second
self.min_interval = 1.0 / calls_per_second
self.last_call_time = 0
def __call__(self, func: Callable) -> Callable:
@functools.wraps(func)
def wrapper(*args, **kwargs) -> Any:
import time
current_time = time.time()
time_since_last = current_time - self.last_call_time
if time_since_last < self.min_interval:
sleep_time = self.min_interval - time_since_last
time.sleep(sleep_time)
self.last_call_time = time.time()
return func(*args, **kwargs)
return wrapper
@RateLimiter(calls_per_second=10)
def api_call(endpoint):
print(f"Calling {endpoint}")방법 5: 메서드 decorator
import functools
def validate_types(*types):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
# Validate positional arguments
for arg, expected_type in zip(args, types):
if not isinstance(arg, expected_type):
raise TypeError(
f"Expected {expected_type}, got {type(arg)}"
)
return func(*args, **kwargs)
return wrapper
return decorator
class Calculator:
@validate_types(int, int)
def add(self, a, b):
return a + b
@validate_types(float, float)
def multiply(self, a, b):
return a * b
calc = Calculator()
print(calc.add(5, 3)) # 8
print(calc.multiply(2.5, 4.0)) # 10.0
try:
calc.add("5", 3)
except TypeError as e:
print(f"Error: {e}")Lessons Learned
functools.wraps 필수: decorator를 작성할 때 반드시
@functools.wraps(func)를 사용하여 원본 함수의 메타데이터를 보존하세요.3중 중첩: decorator에 인자가 필요하면 decorator → decorator factory → wrapper의 3중 중첩 구조를 사용합니다.
호환성 고려:
@decorator와@decorator()두 문법 모두 지원하도록 설계하면 더 유연합니다.클래스 기반 decorator: 복잡한 상태 관리가 필요하면 클래스 기반 decorator를 사용하는 것이 더 깔끔합니다.
타입 힌트: decorator의 파라미터와 반환값에 타입 힌트를 추가하면 IDE 지원이 향상됩니다.
This blog does not accept any external sponsorships, affiliate marketing, or ad revenue.