파이썬을 배워보자 17일차 - Class Decorator

0

Python

목록 보기
17/18

점프 투 파이썬 : 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/

클래스 데코레이터(Decorator)

함수로 데코레이터를 만들듯이, 클래로도 데코레이터를 만들 수 있다. 클래스를 활용할 때는 인스턴스를 함수처럼 호출(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)

데코레이터 문법만 보면 어려워보이는데, 막상 풀어쓰면 그렇게 어렵지 않다.

데코레이터는 디버깅, 로깅, 성능 측정 등 다양한 프로그램 개발에 많이 사용된다. 물론 데코레이터를 마구 사용하는 것은 방법이 아니다. 코드가 복잡해지고 디버깅하기가 어려워지며 너무 많은 데코레이터들은 로직을 이해하기 어렵게 만들기 때문이다.

0개의 댓글