ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 데코레이터(장식자)
    Python 2023. 10. 25. 13:50

    장식하는 도구

    클래스에서 메서드를 만들 때 @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

     

    'Python' 카테고리의 다른 글

    모듈과 패키지 사용  (0) 2023.10.30
    정규표현식  (0) 2023.10.25
    코루틴  (0) 2023.10.24
    제너레이터(발생자)  (0) 2023.10.24
    이터레이터(반복자)  (0) 2023.10.24
Designed by Tistory.