반응형

https://comgu.tistory.com/entry/Python-데코레이터Decorator-1-데코레이터-중첩-데코레이터

위 글에 이어서 데코레이터에 대한 개념을 더 정리하고자, 동적 데코레이터에 대해 다뤄보겠다.

 

동적 데코레이터

동적 데코레이터는 실행 중에 동적으로 조건에 따라 동작하도록 설정할 수 있는 데코레이터를 의미한다.

여러 가지 사용 예시를 통해 동적 데코레이터의 원리에 대해 살펴보자.

 

1. 조건에 따라 데코레이터 함수의 동작을 동적으로 변경할 수 있는 예시

def conditional_decorator(condition):
    def decorator(func):
        if condition:

            def wrapper1(*args, **kwargs):
                print(f"condition is True")
                return func(*args, **kwargs)

            return wrapper1

        else:

            def wrapper2(*args, **kwargs):
                print(f"condition is False")
                return func(*args, **kwargs)

            return wrapper2

    return decorator

conditional_decorator는 내부의 decorator를 정의하며, decorator 함수를 반환한다.

내부의 decorator는 바로 데코레이터 함수 본체이며, 데코레이터가 적용될 함수 func를 파라미터로 받는다. conditional_decorator의 조건인 condition 변수를 참조하며 해당 condition에 따라 다른 wrapper 함수를 반환한다.

wrapper1wrapper2condition에 따라 실행되는 함수이며, 데코레이터를 사용한 함수 func를 호출한다.

만약 True를 아규먼트로 넘긴 conditional_decorator를 데코레이터로 쓰는 아래 함수가 있다면,

@conditional_decorator(True)
def say_hello():
    print("Hello, World!")
    
say_hello()
condition is True
Hello, World!

이는 만약 say_hello()에 데코레이터가 적용이 되지 않았을 경우의에 아래 코드와 동일하다.

conditional_decorator(True)(say_hello)()

 

만약 False를 아규먼트로 넘긴 conditional_decorator를 데코레이터로 쓰는 아래 함수가 있다면,

@conditional_decorator(False)
def say_hello():
    print("Hello, World!")
    
say_hello()
condition is False
Hello, World!

이는 만약 say_hello()에 데코레이터가 적용이 되지 않았을 경우의 아래 코드와 동일하다.

conditional_decorator(False)(say_hello)()

 

conditional_decorator의 파라미터를 더 일반적인 함수 파라미터 형태로 바꿔도 잘 동작한다.

아래처럼 conditional_decorator에서 argskwargs 파라미터로 받게 한뒤, decorator에서 해당 조건에 따라 서로 다른 wrapper 함수를 리턴하도록 해봤다.

def conditional_decorator(*args, **kwargs):
    def decorator(func):
        def wrapper1(*args, **kwargs):
            print("wrapper1 start")
            result = func(*args, **kwargs)
            print("wrapper1 end")
            return result

        def wrapper2(*args, **kwargs):
            print("wrapper2 start")
            result = func(*args, **kwargs)
            print("wrapper2 end")
            return result

        if sum(args) > 10 and kwargs.get("key1", None) == "value1":
            return wrapper1
        else:
            return wrapper2

    return decorator


@conditional_decorator(1, 2, 3, 4, 5, key="value1")
def hello55value1(*args, **kwargs):
    print(sum(args), kwargs.get("k1", None))


@conditional_decorator(0)
def helloNone(*args, **kwargs):
    print(args[0] * args[1] * args[2], kwargs.get("k2", None))


hello55value1(1, 2, 3, k1="!!")
helloNone(5, 7, 9, k2="??")
wrapper1 start
6 !!
wrapper1 end  
wrapper2 start
315 ??        
wrapper2 end

 

만약 hello55value1 함수와 helloNone 함수에 데코레이터 적용이 없었다면, 각각 아래와 같이 함수를 호출한 것과 동일하다.

def hello55value1(*args, **kwargs):
    print(sum(args), kwargs.get("k1", None))


def helloNone(*args, **kwargs):
    print(args[0] * args[1] * args[2], kwargs.get("k2", None))


conditional_decorator(1, 2, 3, 4, 5, key1="value1")(hello55value1)(1, 2, 3, k1="!!")
conditional_decorator(0)(helloNone)(5, 7, 9, k2="??")
wrapper1 start
6 !!
wrapper1 end  
wrapper2 start
315 ??        
wrapper2 end

 

 

2. 조건에 따라 wrapper 함수의 동작을 동적으로 변경할 수 있는 예시

def repeat(n):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for _ in range(n):
                func(*args, **kwargs)

        return wrapper

    return decorator


# 동적 데코레이터 사용
@repeat(n=3)
def greet(name):
    print(f"Hello, {name}!")


greet("John")
Hello, John!
Hello, John!
Hello, John!

repeat 함수는 파라미터 n을 받아 데코레이터를 생성하고 반환한다.

decorator 함수는 데코레이터 함수 본체이며, 데코레이터가 적용될 함수 func를 파라미터로 받는다. wrapper 함수를 반환함으로써 원래 함수(func)에 반복 실행 기능을 추가한다.

wrapper 함수는 데코레이터가 추가 동작을 수행하도록 하는 기능을 외부에서 추가해주는 기능을 추가해준다. 최상위 repeat 함수의 n 변수를 참조하고 있으며, n의 값에 따라 다르게 동작한다(func 함수의 실행 횟수가 바뀐다).

"1. 조건에 따라 데코레이터 함수의 동작을 동적으로 변경할 수 있는 예시" 에서는 decorator 함수가 상위의 조건 변수를 참조했다면, 이 예시에서는 wrapper 함수가 조건 변수(n)을 참조하고 있다는 점에서 다르다.

repeat(n) 데코레이터는 repeat의 정의 시점이 아닌, 실행 시점에 반복 횟수(n)을 동적으로 설정한다. 이를 통해 하나의 데코레이터를 다양한 방식으로 재사용할 수 있다.

 

3. 클래스 메서드에 동적 데코레이터를 적용한 예시

def dynamic_decorator(method_type):
    def decorator(func):
        def wrapper(*args, **kwargs):
            print(f"{method_type} method called")
            return func(*args, **kwargs)

        return wrapper

    return decorator


class MyClass:
    @dynamic_decorator("Instance")
    def instance_method(self, methodtype):
        print(f"{methodtype} method running")

    @staticmethod
    @dynamic_decorator("Static")
    def static_method(methodtype):
        print(f"{methodtype} method running")


obj = MyClass()
obj.instance_method("INSTANCE")
MyClass.static_method("STATIC")
Instance method called
INSTANCE method running
Static method called   
STATIC method running

클래스의 메서드에 동적 데코레이터를 적용하는 방법을 보여주는 예시이다.

dynamic_decoratormethod_type라는 문자열 파라미터를 받고, 데코레이터를 정의 및 리턴한다.

decorator가 리턴하는 wrapper 함수는 method_type를 출력하고 데코레이터를 사용하는 func 함수를 내부에서 실행한다.

MyClass 클래스는 dynamic_decorator 를 사용하는 2가지 메소드를 정의한다.

  • instance_method: 인스턴스를 통해 호출되는 인스턴스 메소드.
  • static_method: 클래스에 바인딩되어 인스턴스와 무관하게 호출되는 메소드. @staticmethod를 사용한다.

 

만약 MyClass 클래스의 두 메소드가 아래와 같이 데코레이터를 사용하지 않았다면,

class MyClass:
    def instance_method(self, methodtype):
        print(f"{methodtype} method running")

    @staticmethod
    def static_method(methodtype):
        print(f"{methodtype} method running")

데코레이터 함수를 사용하기 위해 각각 아래와 같은 함수 호출을 사용한 것과 동일하다.

obj = MyClass()
dynamic_decorator("Instance")(obj.instance_method)("INSTANCE")
dynamic_decorator("Static")(MyClass.static_method)("STATIC")
Instance method called
INSTANCE method running
Static method called   
STATIC method running

 

다음 글에서는 데코레이터의 마지막 주제로, 클래스형 데코레이터에 대해 다뤄보고자 한다.

https://comgu.tistory.com/entry/Python-데코레이터Decorator-3-클래스형-데코레이터

 

[Python] 데코레이터(Decorator) 3 - 클래스형 데코레이터

https://comgu.tistory.com/entry/Python-데코레이터Decorator-1-데코레이터-중첩-데코레이터https://comgu.tistory.com/entry/Python-데코레이터Decorator-2-동적-데코레이터데코레이터에 대한 마지막 주제로, 클래스형 데

comgu.tistory.com

반응형

+ Recent posts