문제의 역사
데코레이터는 파이썬의 가장 강력한 도구 중 하나로 기능이나 메서드의 동작을 수정할 수 있습니다. 때로는 함수만 단순히 "감싸는 것"이 아니라 매개변수(인자)를 사용하여 데코레이터를 설정해야 합니다. 이러한 경우는 로깅, 시간 확인, 접근 제한 등에 사용할 수 있습니다.
문제
일반적인 데코레이터는 감싸야 할 하나의 함수만을 받습니다. 데코레이터에 매개 변수를 전달해야 할 경우, 문법이 더 복잡해져 종종 오류가 발생하고, 특히 함수 중첩 및 *args/**kwargs 전파 시 문제가 발생합니다.
해결책
매개 변수가 있는 데코레이터는 고차 함수로 구현됩니다. 먼저 외부 "데코레이터" 함수가 인자를 사용해 호출되고, 그 함수는 데코레이터를 생성하여 반환하며, 데코레이터는 함수를 받아서 래퍼(wrapper)를 반환합니다:
def repeat(n): def decorator(func): def wrapper(*args, **kwargs): result = None for _ in range(n): result = func(*args, **kwargs) return result return wrapper return decorator @repeat(3) def greet(name): print(f"Hello, {name}!") greet("Python") # 출력: Hello, Python! (3번)
주요 특징:
왜 매개 변수가 있는 데코레이터를 일반적인 데코레이터처럼 구현할 수 없습니까?
@decorator를 사용하면 파이썬은 함수를 인수로 데코레이터에 전달합니다. 괄호를 추가하면 (@decorator()), 파이썬은 먼저 함수를 호출하고, 그 결과만 데코레이터로 해석됩니다.
def deco(func): # 일반적인 데코레이터: @deco def deco_with_args(arg): # 인자가 있는 데코레이터: @deco_with_args(arg)
호출 수준에서 매개 변수가 있는 데코레이터가 매개 변수가 없는 데코레이터와 본질적으로 어떻게 다릅니까?
매개 변수가 없는 데코레이터는 함수 자체를 입력으로 받고, 매개 변수가 있는 데코레이터는 함수를 받지 않고 매개 변수를 받으며, 데코레이터를 반환합니다.
functools.wraps를 올바르게 사용하는 방법과 그 이유는 무엇입니까?
functools.wraps(func)는 래퍼 안에서 원본 함수의 이름, 문서 문자열 및 기타 메타데이터를 유지합니다, 그렇지 않으면 모든 이러한 정보가 래퍼에서 대체되어 디버깅 및 introspection에 방해가 됩니다.
import functools def deco_with_args(arg): def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper return decorator
매개 변수를 고려하지 않거나 잘못된 수의 중첩 함수를 구현했습니다:
def log(level): def wrapper(func): # 오류 - 래퍼는 더 깊어야 함 print(f"Log: {level}") func() # 함수가 데코레이터로 반환되지 않음 return wrapper @log("INFO") def action(): print("Work!")
장점:
단점:
functools.wraps 및 올바른 중첩 함수 사용:
import functools def timer(units): def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): import time start = time.time() result = func(*args, **kwargs) end = time.time() if units == 'ms': duration = (end - start) * 1000 else: duration = end - start print(f"Duration: {duration:.4f} {units}") return result return wrapper return decorator @timer('ms') def op(): sum(range(1000)) op()
장점:
단점: