YOGYUI

Python - Decorator in Class 본문

Software/Python

Python - Decorator in Class

요겨 2021. 7. 5. 16:00
반응형

 

파이썬에서 공통으로 사용되는 구문들은 decorator를 활용해서 코드를 간소화할 수 있다

decorator를 활용한 로깅 예시는 다음과 같다

import logging

def log(func):
    """
    Log what function is called
    """
    def wrap_log(*args, **kwargs):
        name = func.__name__
        logger = logging.getLogger(name)
        logger.setLevel(logging.INFO)

        # add file handler
        fh = logging.FileHandler("%s.log" % name)
        fmt = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
        formatter = logging.Formatter(fmt)
        fh.setFormatter(formatter)
        logger.addHandler(fh)

        logger.info("Running function: %s" % name)
        result = func(*args, **kwargs)
        logger.info("Result: %s" % result)
        return func
    return wrap_log

@log
def double_function(a):
    """
    Double the input parameter
    """
    return a*2

if __name__ == "__main__":
    value = double_function(2)

(출처: https://python101.pythonlibrary.org/chapter25_decorators.html)

 

클래스 내부 메서드들의 공통 구문들도 decorator를 활용해 코드를 간소화할 수 있는데, 일반적인  decorator 사용법에 조금의 트릭만 가미하면 된다

 

예를 위해 다음과 같이 클래스 MyClass1을 구현했다

import datetime

class MyClass1:
    name = 'YogyuiClass1'
    
    def method1(self):
        print('Called Time: ' + datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
        # print(f'{type(self).__name__}::method1 is called')
        print(f'{self.name}::method1 is called')        
    
    def method2(self, value: int) -> int:
        print('Called Time: ' + datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
        # print(f'{type(self).__name__}::method2 is called')
        print(f'{self.name}::method2 is called')
        return value + 1
    
    def method3(self, value1: int, value2: int) -> float:
        print('Called Time: ' + datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
        # print(f'{type(self).__name__}::method3 is called')
        print(f'{self.name}::method3 is called')
        try:
            return value1 / value2
        except ZeroDivisionError:
            print('ZeroDivisionError Occurred')

method1은 함수 인자 없이 단순 문자열 출력, method2는 정수형 인자를 입력받아서 1 더한 값을 리턴, method2는 정수형 인자 2개를 입력 받아서 나눈 값을 리턴하도록 작성했으며, 모든 메서드들은 호출 시 호출 시점을 출력하도록 구현했다

 

각 메서드들을 호출해보자

In [1]: test = MyClass1()

In [2]: print(test.method1())
Called Time: 2021-07-05 15:39:33
YogyuiClass1::method1 is called
None

In [3]: print(test.method2(1))
Called Time: 2021-07-05 15:39:37
YogyuiClass1::method2 is called
2

In [4]: print(test.method3(1, 2))
Called Time: 2021-07-05 15:39:42
YogyuiClass1::method3 is called
0.5

In [5]: print(test.method3(1, 0))
Called Time: 2021-07-05 15:39:45
YogyuiClass1::method3 is called
ZeroDivisionError Occurred
None

모든 메서드들에 대해 호출 시점을 출력하는 함수를 추가해야 한다고 생각하면 코드가 길어질 수 밖에 없다

이 때, 다음과 같이 클래스 내부에서 객체를 참조할 수 있는 Inner Class를 구현한 후, decorator로 활용하면 공통 구문을 손쉽게 하나로 합칠 수 있다

 

MyClass1 메서드들의 공통 구문을 하나로 묶은 MyClass2를 다음과 같이 구현해보았다

class MyClass2:
    name = 'YogyuiClass2'
    
    class MyDecorator(object):
        @staticmethod
        def decorator(func):
            def wrapper(self, *args, **kwargs):
                print('Called Time: ' + datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
                print(f'{self.name}::{func.__name__} is called')
                try:
                    return func(self, *args, **kwargs)
                except ZeroDivisionError:
                    print('ZeroDivisionError Occurred')
            return wrapper
    
    @MyDecorator.decorator
    def method1(self):
        pass
    
    @MyDecorator.decorator
    def method2(self, value: int):
        return value + 1
    
    @MyDecorator.decorator
    def method3(self, value1: int, value2: int) -> float:
        return value1 / value2
In [6]: test = MyClass2()

In [7]: print(test.method1())
Called Time: 2021-07-05 15:47:55
YogyuiClass2::method1 is called
None

In [8]: print(test.method2(1))
Called Time: 2021-07-05 15:47:58
YogyuiClass2::method2 is called
2

In [9]: print(test.method3(1, 2))
Called Time: 2021-07-05 15:48:03
YogyuiClass2::method3 is called
0.5

In [10]: print(test.method3(1, 0))
Called Time: 2021-07-05 15:48:04
YogyuiClass2::method3 is called
ZeroDivisionError Occurred
None

 

Class 내부에 구현된 inner-Class가 부모(엄밀히 말하면 부모는 아니지만) 객체를 참조하는 약간의 트릭만 추가하면 손쉽게 데코레이터 객체를 만들 수 있고, 메서드들의 공통 구문들을 하나로 묶어서 코드를 간소화할 수 있다 (굳이 functool의 wraps를 사용하지 않아도 직관적으로 구현 가능하다)

 

개인적인 경험으로는, 디버깅을 위한 logging 구현 시 엄청 편해지고, 위와 같이 try-except문도 decorator단에서 처리할 수 있기 때문에 불필요한 code indent도 방지할 수 있어서 코드가 굉장히 간결해진다 (협업 시 co-worker들은 decorator 구문 신경쓰지 않고 본인의 코드만 구현할 수 있다)

 

끝~!

 

[참고]

https://stackoverflow.com/questions/1263451/python-decorators-in-classes

반응형