점프 투 파이썬 : https://wikidocs.net/book/1
파이썬 기본을 갈고 닦자 : https://wikidocs.net/16031
코딩 도장 : https://dojang.io/mod/page/view.php?id=2378
GeeksforGeeks : https://www.geeksforgeeks.org/decorators-in-python/
함수로 데코레이터를 만들듯이, 클래로도 데코레이터를 만들 수 있다. 클래스를 활용할 때는 인스턴스를 함수처럼 호출(instance()와 같이 호출
)하게 해주는 __call__
메서드를 구현해야한다.
class Log:
def __init__(self, func): # 호출할 함수를 인스턴스의 초기값으로 받음
self.func = func # 호출할 함수를 속성 func에 저장
def __call__(self):
print(self.func.__name__, "start func")
self.func()
print(self.func.__name__,"end func")
@Log
def hello():
print("hello world")
hello()
다음과 같이 데코레이터인 Log
를 함수 hello
에게 얹어놓으면 데코레이터가 적용된다.
hello 함수 시작
hello
hello 함수 끝
하나하나 확인해보면, 다음의 코드는 __init__
메서드를 만들어 데코레이터로 감싼 함수를 초기값으로 받는다. 맴버속성으로 저장하기위해서 self.func = func
으로 넣어두도록 하자.
class Log:
def __init__(self, func): # 호출할 함수를 인스턴스의 초기값으로 받음
self.func = func # 호출할 함수를 속성 func에 저장
다음으로, 인스턴스를 호출할 수 있도록 __call__
메서드를 만들어야 한다. 이 부분이 func
으로 받은 함수, 즉 데코레이터로 감싼 함수를 가져와 처리하는 로직부분이다.
def __call__(self):
print(self.func.__name__, '함수 시작') # __name__으로 함수 이름 출력
self.func() # 속성 func에 저장된 함수를 호출
print(self.func.__name__, '함수 끝')
일반적인 사용방법은 다음과 같다.
@데코레이터
def 함수이름():
코드
사실, 데코레이터를 문법(@decorator
)을 사용하지 않고 HOF(high order function) 방법으로도 구현할 수 있는데, 이 방법과 @decorator
문법이 사실 동일한 방법이다. 그래서 __call__
의 구현이 필요한 것이다.
log_hello = Log(hello)
log_hello() # __call__이 호출된다.
클래스로 만든 데코레이터도 매개변수와 반환값을 처리할 수 있다. 다음은 함수의 매개변수를 출력하는 데코레이터이다.
class Log:
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의 반환값을 반환
@Log # 데코레이터
def add(a, b):
return a + b
print("-----------------")
print(add(10, 20))
print("-----------------")
print(add(a=10, b=20))
print("-----------------")
결과는 다음과 같다.
-----------------
add(args=(10, 20), kwargs={}) -> 30
30
-----------------
add(args=(), kwargs={'a': 10, 'b': 20}) -> 30
30
-----------------
클래스로 매개변수와 반환값을 처리하는 데코레이터를 만들 때는 __call__
메서드에 매개변수를 지정하고, self.func
에 매개변수를 넣어서 호출한 뒤에 반환값을 반환해주면 된다.
참고로, 가변인수로 만들지 않고 고정된 매개변수를 사용해서 사용해도 된다.
데코레이터가 값을 받아서, 처리할 때 조금 모습이 변하게 되는데 다음과 같다.
class Mul:
def __init__(self, x, y): # 데코레이터 인수를 받음
self.x = x
self.y = y # 인수를 맴버변수로 저장
def __call__(self,func):
def wrapper(a,b): # 매개변수를 받는 wrapper 작성
r = func(a,b)
return r * (self.x + self.y)
return wrapper
@Mul(1,2) # 데코레이터
def add(a,b):
return a + b
print(add(10, 20))
결과는 다음과 같다.
90
다음의 Mul
데코레이터는 매개변수로 x,y
를 받아 x+y
를 데코레이터로 감싼 함수에 곱해주는 기능을 한다.
class Mul:
def __init__(self, x, y): # 데코레이터 인수를 받음
self.x = x
self.y = y # 인수를 맴버변수로 저장
데코레이터가 매개변수를 받기 시작하면 더 이상 __init__
부분에 func
을 받지 않는다. 대신 매개변수를 받게 되므로 이를 맴버 속성(변수)로 저장하도록 하자.
def __call__(self,func):
def wrapper(a,b): # 매개변수를 받는 wrapper 작성
r = func(a,b)
return r * (self.x + self.y)
return wrapper
__call__
함수에는 더 이상 데코레이터로 감싼 함수의 매개변수를 받지 않는다. 대신에 func
을 여기서 받게 된다. 데코레이터로 감싼 함수의 매개변수 add(10, 20)
를 받기위해서는 하나의 wrapper
를 만들어주어야 한다.
동작을 구체화하면 다음과 같기 때문이다.
mul = Mul(1,2)
wrapper = mul(add)
res = wrapper(10,20)
print(res)
데코레이터 문법만 보면 어려워보이는데, 막상 풀어쓰면 그렇게 어렵지 않다.
데코레이터는 디버깅, 로깅, 성능 측정 등 다양한 프로그램 개발에 많이 사용된다. 물론 데코레이터를 마구 사용하는 것은 방법이 아니다. 코드가 복잡해지고 디버깅하기가 어려워지며 너무 많은 데코레이터들은 로직을 이해하기 어렵게 만들기 때문이다.