데코레이터(@)는 함수를 수정하지 않은 상태에서 추가 기능을 구현할 때 사용한다.
다음은 함수의 시작과 끝을 출력하는 데코레이터다.
def trace(func): # 호출할 함수를 매개변수로 받음
def wrapper(): # 호출할 함수를 감싸는 함수
print(func.__name__, '함수 시작') # __name__으로 함수 이름 출력
func() # 매개변수로 받은 함수를 호출
print(func.__name__, '함수 끝')
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 함수 끝
호출할 함수 위에 @데코레이터 형식으로 지정한다.
def trace(func): # 호출할 함수를 매개변수로 받음
def wrapper():
print(func.__name__, '함수 시작') # __name__으로 함수 이름 출력
func() # 매개변수로 받은 함수를 호출
print(func.__name__, '함수 끝')
return wrapper # wrapper 함수 반환
@trace # @데코레이터
def hello():
print('hello')
@trace # @데코레이터
def world():
print('world')
hello() # 함수를 그대로 호출
world() # 함수를 그대로 호출
실행 결과는 처음 코드와 같다.
데코레이터를 여러 개 지정할 수도 있다. 실행 순서는 위에서 아래 순이다.
@데코레이터1
@데코레이터2
def 함수이름():
코드
만약 wrapper 함수에서 func의 반환값을 반환하지 않으면 add 함수를 호출해도 반환값이 나오지 않는다. wrapper 함수에서 func의 반환값을 출력할 필요가 없으면 return func(a, b)처럼 func를 호출하면서 바로 반환해도 된다.
def trace(func): # 호출할 함수를 매개변수로 받음
def wrapper(*args, **kwargs): # 가변 인수 함수로 만듦
r = func(*args, **kwargs) # func에 args, kwargs를 언패킹하여 넣어줌
print('{0}(args={1}, kwargs={2}) -> {3}'.format(func.__name__, args, kwargs, r))
# 매개변수와 반환값 출력
return r # func의 반환값을 반환
return wrapper # wrapper 함수 반환
@trace # @데코레이터
def get_max(*args): # 위치 인수를 사용하는 가변 인수 함수
return max(args)
@trace # @데코레이터
def get_min(**kwargs): # 키워드 인수를 사용하는 가변 인수 함수
return min(kwargs.values())
print(get_max(10, 20))
print(get_min(x=10, y=20, z=30))
# 실행 결과
get_max(args=(10, 20), kwargs={}) -> 20
20
get_min(args=(), kwargs={'x': 10, 'y': 20, 'z': 30}) -> 10
10
이렇게 만든 데코레이터는 가변 인수 함수뿐만 아니라 일반적인 함수에도 사용할 수 있다.
이런 방식의 데코레이터는 값을 지정해서 동작을 바꿀 수 있다.
다음은 함수의 반환값이 특정 수의 배수인지 확인하는 데코레이터다.
def is_multiple(x): # 데코레이터가 사용할 매개변수를 지정
def real_decorator(func): # 호출할 함수를 매개변수로 받음
def wrapper(a, b): # 호출할 함수의 매개변수와 똑같이 지정
r = func(a, b) # func를 호출하고 반환값을 변수에 저장
if r % x == 0: # func의 반환값이 x의 배수인지 확인
print('{0}의 반환값은 {1}의 배수입니다.'.format(func.__name__, x))
else:
print('{0}의 반환값은 {1}의 배수가 아닙니다.'.format(func.__name__, x))
return r # func의 반환값을 반환
return wrapper # wrapper 함수 반환
return real_decorator # real_decorator 함수 반환
@is_multiple(3) # @데코레이터(인수)
def add(a, b):
return a + b
print(add(10, 20))
print(add(2, 5))
# 실행 결과
add의 반환값은 3의 배수입니다.
30
add의 반환값은 3의 배수가 아닙니다.
7
이처럼 매개변수가 있는 데코레이터는 함수를 하나 더 만들어야 한다.
클래스를 활용할 때는 인스턴스를 함수처럼 호출하게 해주는 __call__ 메서드를 구현해야 한다.
class Trace:
def __init__(self, func): # 호출할 함수를 인스턴스의 초깃값으로 받음
self.func = func # 호출할 함수를 속성 func에 저장
def __call__(self):
print(self.func.__name__, '함수 시작') # __name__으로 함수 이름 출력
self.func() # 속성 func에 저장된 함수를 호출
print(self.func.__name__, '함수 끝')
@Trace # @데코레이터
def hello():
print('hello')
hello() # 함수를 그대로 호출
# 실행 결과
hello 함수 시작
hello
hello 함수 끝
클래스로 만든 데코레이터는 @을 지정하지 않고, 데코레이터의 반환값을 호출하는 방식으로도 사용할 수 있다.
def hello(): # @데코레이터를 지정하지 않음
print('hello')
trace_hello = Trace(hello) # 데코레이터에 호출할 함수를 넣어서 인스턴스 생성
trace_hello() # 인스턴스를 호출. __call__ 메서드가 호출됨
__call__ 메서드에 매개변수를 지정하고, self.func에 매개변수를 넣어서 호출한 뒤에 반환값을 반환해주면 된다.
class Trace:
def __init__(self, func): # 호출할 함수를 인스턴스의 초깃값으로 받음
self.func = func # 호출할 함수를 속성 func에 저장
def __call__(self, *args, **kwargs): # 호출할 함수의 매개변수를 처리
r = self.func(*args, **kwargs) # self.func에 매개변수를 넣어서 호출하고 반환값을 변수에 저장
print('{0}(args={1}, kwargs={2}) -> {3}'.format(self.func.__name__, args, kwargs, r))
# 매개변수와 반환값 출력
return r # self.func의 반환값을 반환
@Trace # @데코레이터
def add(a, b):
return a + b
print(add(10, 20))
print(add(a=10, b=20))
# 실행 결과
add(args=(10, 20), kwargs={}) -> 30
30
add(args=(), kwargs={'a': 10, 'b': 20}) -> 30
30
나머지는 동일
class IsMultiple:
def __init__(self, x): # 데코레이터가 사용할 매개변수를 초깃값으로 받음
self.x = x # 매개변수를 속성 x에 저장
def __call__(self, func): # 호출할 함수를 매개변수로 받음
def wrapper(a, b): # 호출할 함수의 매개변수와 똑같이 지정(가변 인수로 작성해도 됨)
r = func(a, b) # func를 호출하고 반환값을 변수에 저장
if r % self.x == 0: # func의 반환값이 self.x의 배수인지 확인
print('{0}의 반환값은 {1}의 배수입니다.'.format(func.__name__, self.x))
else:
print('{0}의 반환값은 {1}의 배수가 아닙니다.'.format(func.__name__, self.x))
return r # func의 반환값을 반환
return wrapper # wrapper 함수 반환
@IsMultiple(3) # 데코레이터(인수)
def add(a, b):
return a + b
print(add(10, 20))
print(add(2, 5))
# 실행 결과
add의 반환값은 3의 배수입니다.
30
add의 반환값은 3의 배수가 아닙니다.
7