이전 글 [파이썬] 데코레이터 (1)에 이어서 작성
여러 기능과 같이 사용 가능
함수, 클래스뿐만이 아니라 제네레이터, 코루틴, 데코레이트된 객체에도 사용 가능하다.
Stack으로 쌓기
아래처럼 여러 개를 함께 사용할 수 있다.
@DataClass
@Serialization(
username=show_original,
password=hide_field,
ip=show_original,
timestamp=format_time,
)
class LoginEvent:
중첩 함수, 고차 함수
데코레이터의 구조는 함수 안의 함수로 중첩함수 형태다. 여기에 함수를 인자로 받고 함수를 반환하는 고차 함수의 구조도 가져간다.
아래 예제를 보면 wrapped 내에서 정의한 함수를 반환하여, 외부 함수trace가 종료되더라도 내부 함수는 유지되는 클로저 형태이다.
def trace(fn):
@wraps(fn)
def wrapped(*args, **kwargs):
print("before", fn.__name__)
result = fn(*args, **kwargs)
print("after", fn.__name__)
return result
return wrapped
데코레이터는 래핑하여 반환하기에 함수명과 docstring이 변경된다. 이후에 디버깅 또는 오류 추적시 문제가 될 수 있다.
from functools import wraps
def trace(fn):
def wrapped(*args, **kwargs):
"""trace 함수"""
print("before", fn.__name__)
result = fn(*args, **kwargs)
print("after", fn.__name__)
return result
return wrapped
@trace
def hello(name):
"""hello 출력 함수"""
print("Hello,", name)
print(hello) #=> <function trace.<locals>.wrapped at 0xa9a7c0>
help(hello) #=> trace 함수
이처럼 데코레이터 함수의 정보가 출력된다. 이를 방지하기 위해 @wraps 사용을 권장한다.
from functools import wraps
def trace(fn):
@wraps(fn)
def wrapped(*args, **kwargs):
# ...
return wrapped
@trace
def hello(name):
"""hello 출력 함수"""
print("Hello,", name)
print(hello) #=> <function hello at 0x13d9b60>
help(hello) #=> hello 출력 함수
이렇게 함으로 원본 함수를 유지할 수 있다. 물론 __wrapped__로 원본 함수에도 접근할 수 있다. 보통 테스트시 사용한다.
print(hello.__wrapped__("User")) #=> trace가 붙지 않은 원본 함수 출력
마지막으로 책에서 언급하는 데코레이터 사용시 규칙으로 글을 마무리한다.
DRY를 따르자
처음부터 데코레이터를 만드는 것이 아니라 일정 패턴이 생겨 추상화가 필요할 때, Do not repeat yourself를 실천하면 된다.
최소 3번 이상의 패턴이 있을 때 쓰며, 데코레이터 코드 자체는 최소한으로 유지해야 한다.