
@데코레이터 를 붙여서 사용한다.wrapper 라고 한다.def hello(): print('hello 함수 시작') print('hello') print('hello 함수 끝') def world(): print('world 함수 시작') print('world') print('world 함수 끝') hello() world()#결과 hello 함수 시작 hello hello 함수 끝 world 함수 시작 world world 함수 끝
def trace(func): # 호출할 함수를 매개변수로 받음 def wrapper(): # 호출할 함수를 감싸는 함수 print(func.__name__, '함수 시작') # __name__으로 함수 이름 출력 func() # 매개변수로 받은 함수를 호출 print(func.__name__, '함수 끝') print('*****') return wrapper # wrapper 함수 반환 def hello(): print('hello') def world(): print('world') trace_hello = trace(hello) # 데코레이터에 호출할 함수를 넣음 trace_hello() # 반환된 함수를 호출 trace_world = trace(world) # 데코레이터에 호출할 함수를 넣음 trace_world() # 반환된 함수를 호출#결과 hello 함수 시작 hello hello 함수 끝 ***** world 함수 시작 world world 함수 끝 *****
# 데코레이터 적용하기 @trace def hello(): print('hello') @trace def world(): print('world') # 함수 그대로 호출 hello() world()# 결과 hello 함수 시작 hello hello 함수 끝 ***** world 함수 시작 world world 함수 끝 *****
: 매개변수와 반환값을 처리하는 데코레이터
def wrapper(a, b), add(a, b)와 같이 호출할 함수의 매개변수와 똑같이 지정한다.def trace(func): # 호출할 함수를 받음 def wrapper(a, b): # 호출할 함수의 매개변수와 똑같이 지정 ex) add(a, b) r = func(a,b) # 반환값을 r변수에 저장 # 매개변수와 반환값 출력 pring('{0}(a={1}, b={2}) -> {3}'.format(func.__name__, a, b, r) return r # func의 반환값을 반환 return wrapper # wrapper함수 반환 @trace def add(a, b): return a+b print(add(10,20)# 결과 add(a=10, b=20) -> 30 30
is_multiple(x): 반환값이 특정 수의 배수인지 확인하는 데코레이터def is_multiple(x): def real_decorator(func): def wrapper(a, b): r = func(a, b) if r % x ==0 : print('{0}의 반환값은 {1}의 배수입니다.'.format(func.__name__, x)) else: print('{0}의 반환값은 {1}의 배수가 아닙니다.'.format(func.__name__, x)) return r # func의 반환값을 반환 return wrapper return real_decorator
- 적용
@is_multiple(3) def add(a,b): return a+b
- 결과
# 결과 print(add(10, 20)) # add의 반환값은 3의 배수입니다. # 30 print(add(2, 5)) # add의 반환값은 3의 배수가 아닙니다. # 7
*args: 튜플 형태로 변수 저장**kwargs : 딕셔너리 형태로 변수 저장 def add(a,b)는 매개변수의 개수가 고정된 함수였다. 매개변수(인수)가 고정되지 않은 함수를 처리할 때는, wrapper함수를 가변 인수 함수로 만들면 된다.
- 데코레이터 실행
def trace(func): def wrapper(*args, **kwargs): r = func(*args, **kwargs) print('{0}(args={1}, kwargs={2}) -> {3}'.format(func.__name__, args, kwargs, r)) return r return wrapper @trace def get_max(*args): # 튜플 형태 인수를 사용 return max(args) @trace def get_min(**kwargs): # 딕셔너리 형태 인수를 사용 return min(kwargs.values())
- 결과확인
print(get_max(10,20)) #get_max(args=(10, 20), kwargs={}) -> 20 #20 print(get_min(x=1,y=2,z=3,q=5)) #get_min(args=(), kwargs={'x': 1, 'y': 2, 'z': 3, 'q': 5}) -> 1 #1
@functools.wraps는 원래 함수의 정보를 유지시켜주어 디버깅과 문서화할 때 유용하다.
- 여러개 데코레이터 지정
@is_multiple(3) @is_multiple(7) def add(a, b): return a + b add(10, 20)
- 결과
add의 반환값은 7의 배수가 아닙니다. wrapper의 반환값은 3의 배수입니다.-> 가까운 decorator먼저 적용
-> 데코레이터를 여러 개 사용하면 데코레이터에서 반환된wrapper함수가 다른 데코레이터로 들어가기 때문에, 함수의func.__name__을 출력해보면wrapper가 나옴
- 따라서!!
=> 함수의 원래 이름을 출력, signature를 잘 보존하고 싶다면from functools import wraps을 가져와 @wraps에 func을 넣은 뒤, wrapper함수 위에 지정해주어야 한다.
- @wraps 사용
from functools import wraps def is_multiple(x): def real_decorator(func): @wraps(func) # @wraps에 func를 넣은 뒤 wrapper 함수 위에 지정 def wrapper(a, b): r = func(a, b) if r % x == 0: print('{0}의 반환값은 {1}의 배수입니다.'.format(func.__name__, x)) else: print('{0}의 반환값은 {1}의 배수가 아닙니다.'.format(func.__name__, x)) return r return wrapper return real_decorator @is_multiple(3) @is_multiple(7) def add(a, b): return a + b add(10, 20)
- 결과
add의 반환값은 7의 배수가 아닙니다. add의 반환값은 3의 배수입니다.->원래 함수의 정보를 유지시켜주기 때문에
func.__name__값이 원래대로 나온다.