Python/Python

Decorator

하효닝 2022. 3. 17. 03:12

데코레이터

  • 클래스에서 메서드를 만들 때 @staticmethod, @classmethod, @abstractmethod처럼 @로 시작하는 것들이 데코레이터
  • 데코레이터는 함수를 수정하지 않은 상태에서 추가 기능을 구현할 때 사용한다.
  • 예를 들어 함수의 시작과 끝을 출력하고 싶은 경우 아래와 같이 작성할 수 있다.
def trace(func):                             # 호출할 함수를 매개변수로 받음
    def wrapper():                           # 호출할 함수를 감싸는 함수
        print(func.__name__, '함수 시작')    # __name__으로 함수 이름 출력
        func()                               # 매개변수로 받은 함수를 호출
        print(func.__name__, '함수 끝')
    return wrapper                           # wrapper 함수 반환
 
def hello():
    print('hello')
 
trace_hello = trace(hello)    # 데코레이터에 호출할 함수를 넣음
trace_hello()                 # 반환된 함수를 호출

 

@데코레이터

  • 호출될 함수 위에 @데코레이터 형식으로 지정하면 더 간편하게 데코레이터를 사용할 수 있다.
  • 데코레이터는 함수를 감싸는 형태로 구성되기 때문에, 기존 함수를 수정하지 않으면서 추가 기능을 구현할 때 사용한다.
@데코레이터
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()    # 함수를 그대로 호출

 

동작 과정

매개변수와 반환값 처리

  • 매개변수와 반환값을 처리하는 데코레이터를 만들 때는 먼저 안쪽 함수(wrapper)의 매개변수를 호출할 함수(add)의 매개변수와 똑같이 만들어 준다.
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 함수를 호출했을 때 데코레이터를 통해서 매개변수와 반환값이 출력된다.

 

가변 인수 함수

  • 매개변수가 고정되지 않은 함수를 처리하려면  wrapper 함수를 가변 인수 함수로 만든다.
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))

 

 

매개변수가 있는 데코레이터

  • 매개변수가 있는 데코레이터는 값을 지정해서 동작을 바꿀 수 있으며, 만들 때 함수를 하나 더 만들어야 한다.
  • 데코레이터를 사용할 때는 데코레이터에 괄호를 붙인 뒤 인수를 넣어주면 된다.
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))
  • 먼저 is_multiple 함수를 만들고 데코레이터가 사용할 매개변수 x를 지정한다.
  • 그 다음 is_multiple 함수 안에서 실제 데코레이터 역할을 하는 real_decorator를 만든다.
  • 즉, 이 함수에서 호출할 함수를 매개변수로 받고, real_decorator 함수 안에서 wrapper 함수를 만든다.

 

클래스

  • 클래스를 활용할 때는 인스턴스를 함수처럼 호출하게 해주는 __call__ 메서드를 구현해야 한다.
  • 클래스로 데코레이터를 만들 때는 먼저 __init__ 메서드 에서 호출할 함수를 초기값으로 받은 다음, 매개변수로 받은 함수를 속성으로 지정한다.
  • __call__ 함수에서는 속성 func에 지정된 함수를 호출한다.
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()    # 함수를 그대로 호출

 

 

매개변수와 반환값 처리

  • 클래스로 매개변수와 반환값을 처리하는 데코레이터를 만들 때는 __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))

 

매개변수가 있는 데코레이터

  • __init__ 메서드에서 데코레이터가 사용할 매개변수를 초기값으로 받고, 매개변수를 __call__ 메서드에서 사용할 수 있도록 속성에 지정한다.
  • __call__ 메서드에서는 호출할 함수를 매개변수로 받고, __call__ 메서드 안에 wrapper 함수를 만들어준다.
  • 이때 wrapper 함수의 매개변수는 호출할 함수의 매개변수와 똑같이 지정한다.
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))