데코레이터(장식자)
장식하는 도구
클래스에서 메서드를 만들 때 @staticmethod, @classmethod, @abstractmethod 등을 붙였는데 @로 시작하는 것들이 데코레이터
> 함수(메서드)를 장식
데코레이터 만들기
데코레이터는 함수를 수정하지 않은 상태에서 추가 기능을 구현할 때 사용
예를 들어 지금까지는 시작과 끝을 출력하고 싶다면 함수 시작, 끝 부분에 print를 넣어야 함 > 번거로움
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__, '함수 끝')
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는 호출할 함수를 매개변수로 받음(trace는 추적하다란 뜻, 프로그래밍에서 함수의 실행 상황을 추적할 때 trace 사용)
wrapper는 함수를 감싼다는 뜻에서 사용했는데, 다른 이름 사용해도 상관없음
매개변수로 받은 함수의 원래 이름 출력할 때 __name__ 속성 활용
> 함수 안에서 함수를 만들고 반환하는 클로저
@으로 데코레이터 사용
# @데코레이터
# def 함수이름():
# 코드
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()
# 실행 결과
hello 함수 시작
hello
hello 함수 끝
world 함수 시작
world
world 함수 끝
데코레이터는 함수를 감싸는 형태로 구성되어 있어 기존 함수를 수정하지 않으면서 추가 기능을 구현할 때 사용
데코레이터 여러 개 지정
# @데코레이터1
# @데코레이터2
# def 함수이름():
# 코드
def decorator1(func):
def wrapper():
print('decorator1')
func()
return wrapper
def decorator2(func):
def wrapper():
print('decorator2')
func()
return wrapper
@decorator1
@decorator2
def hello():
print('hello')
hello()
# 실행 결과
decorator1
decorator2
hello
@를 사용하지 않았을 때는 이 코드와 동작이 같음
decorated_hello = decorator1(decorator2(hello))
decorated_hello()
매개변수와 반환값을 처리하는 데코레이터 만들기
def trace(func): # 호출할 함수를 매개변수로 받음
def wrapper(a, b): # 호출할 함수 add(a, b)의 매개변수와 똑같이 지정
r = func(a, b) # func에 매개변수 a, b 넣어 호출하고 반환값 변수에 저장
print('{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
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:
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))
print(add(2, 5))
# 실행 결과
add의 반환값은 3의 배수
30
add의 반환값은 3의 배수가 아님
7
앞에서는 데코레이터 만들 때 함수 안에 함수를 하나만 만들었지만, 매개변수 있는 데코레이터를 만들 때는 함수를 하나 더 만들어야 함
1. is_multiple 함수를 만들고 데코레이터가 사용할 매개변수 x 지정
2. is_multiple 함수 안에서 실제 데코레이터 역할을 하는 real_decorator 만듦
> 이 함수에서 호출할 함수를 매개변수로 받음
3. real_decorator 함수 안에서 wrapper 함수 만듦
데코레이터 사용할 때는
@데코레이터(인수)
def 함수이름():
코드
클래스로 데코레이터 만들기
클래스를 활용할 때는 인스턴스를 함수처럼 호출하게 해 주는 __call__ 메서드 구현
class Trace:
def __init__(self, func): # 호출할 함수를 인스턴스의 초깃값으로 받음
self.func = func # 호출할 함수를 속성 func에 저장
def __call__(self):
print(self.func.__name__, '함수 시작')
self.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__ 메서드가 호출됨
클래스로 매개변수와 반환값을 처리하는 데코레이터 만들기
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에 저장(__call__ 메서드에서 사용할 수 있도록)
def __call__(self, func): # 호출할 함수를 매개변수로 받음
def wrapper(a, b): # 호출할 함수의 매개변수와 똑같이 지정
r = func(a, b)
if r % self.x == 0:
print('{}의 반환값은 {}의 배수'.format(func.__name__, self.x))
else:
print('{}의 반환값은 {}의 배수가 아님'.format(func.__name__, self.x))
return r
return wrapper
@IsMultiple(3)
def add(a, b):
return a + b
print(add(10, 20))
print(add(2, 5))
# 실행 결과
add의 반환값은 3의 배수
30
add의 반환값은 3의 배수가 아님
7