본문 바로가기

Python/Intermediate

[Python] Decorator

- 장점

중복 제거, 코드 간결, 공통 함수 작성

로깅, 프레임워크, 유효성 체크..... -> 공통 기능

조합해서 사용 용이

 

 

- 단점

가독성 감소

특정 기능에 한정된 함수는 -> 단일 함수로 작성하는 것이 유리

디버깅 불편

 

 

Decorator 미사용 샘플 코드

 

# 데코레이터 실습 - 미사용
import time

def perf_clock(func):
    def perf_clocked(*args):
        # 함수 시작 시간 
        st = time.perf_counter() 
        result = func(*args)
        # 함수 종료 시간 계산
        et = time.perf_counter() - st 
        # 실행 함수명
        name = func.__name__
        # 함수 매개변수 
        arg_str = ', '.join(repr(arg) for arg in args)
        # 결과 출력
        print('[%0.5fs] %s(%s) -> %r' % (et, name, arg_str, result)) 
        return result 
    return perf_clocked

def time_func(seconds):
    time.sleep(seconds)

def sum_func(*numbers):
    return sum(numbers)
    
# 데코레이터 미사용
none_deco1 = perf_clock(time_func)
none_deco2 = perf_clock(sum_func)

print(none_deco1, '***', none_deco1.__code__.co_freevars)
print(none_deco2, '***', none_deco2.__code__.co_freevars)

print()
print('-' * 40, 'Called None Decorator -> time_func')
none_deco1(1.5)
print()
print('-' * 40, 'Called None Decorator -> sum_func')
none_deco2(100, 150, 250, 300, 350)

--------------------------------------------[result]

<function perf_clock.<locals>.perf_clocked at 0x0000016FB8AF77B8> *** ('func',)
<function perf_clock.<locals>.perf_clocked at 0x0000016FB8BBC048> *** ('func',)

---------------------------------------- Called None Decorator -> time_func
[1.50439s] time_func(1.5) -> None

---------------------------------------- Called None Decorator -> sum_func
[0.00000s] sum_func(100, 150, 250, 300, 350) -> 1150
1150

 

Decorator 샘플 코드

 

# 데코레이터 실습
import time

def perf_clock(func):
    def perf_clocked(*args):
        # 함수 시작 시간 
        st = time.perf_counter() 
        result = func(*args)
        # 함수 종료 시간 계산
        et = time.perf_counter() - st 
        # 실행 함수명
        name = func.__name__
        # 함수 매개변수 
        arg_str = ', '.join(repr(arg) for arg in args)
        # 결과 출력
        print('[%0.5fs] %s(%s) -> %r' % (et, name, arg_str, result)) 
        return result 
    return perf_clocked

@perf_clock # 주석처리안하면 데코레이터 미사용 예제에서 출력이 이상해짐.
def time_func(seconds):
    time.sleep(seconds)

@perf_clock # 주석처리안하면 데코레이터 미사용 예제에서 출력이 이상해짐.
def sum_func(*numbers):
    return sum(numbers)
    
# 데코레이터 사용
print('*' * 40, 'Called Decorator -> time_func')
time_func(1.5)
print(time_func(1.5))
print('*' * 40, 'Called Decorator -> sum_func')
sum_func(100, 150, 250, 300, 350)
print(sum_func(100, 150, 250, 300, 350))

--------------------------------------------[result]

[1.50258s] time_func(1.5) -> None
[1.50364s] time_func(1.5) -> None
None
**************************************** Called Decorator -> sum_func
[0.00000s] sum_func(100, 150, 250, 300, 350) -> 1150
[0.00000s] sum_func(100, 150, 250, 300, 350) -> 1150
1150

 

 

다양한 방식의 데코레이터 샘플 코드 - 고정 인수

 

def trace(func):
    def wrapper(a,b):
        r = func(a,b)
        print('{0}(a={1}, b={2}) ==> {3}'.format(func.__name__,a,b,r))
        return r
    return wrapper


@trace
def add(a,b):
    return a+b

print(add(11,22))

------------------------------------- result

add(a=11, b=22) ==> 33
33

 

 

가변 인수로 구현한 샘플 코드

 

def trace(func):
    def wrapper(*args, **kargs):
        r = func(*args, **kargs)
        print('{0}(args={1}, kargs={2}) ==> {3}'.format(func.__name__, args, kargs, r))
        return r
    return wrapper

@trace
def get_max(*args):
    return max(args)

@trace
def get_min(**kargs):
    return min(kargs.values())

print(get_max(1,2,3,44,55,66,7,8,9))
print(get_min(a=10,b=30,c=50,d=1))

-------------------------------------

get_max(args=(1, 2, 3, 44, 55, 66, 7, 8, 9), kargs={}) ==> 66
66
get_min(args=(), kargs={'a': 10, 'b': 30, 'c': 50, 'd': 1}) ==> 1
1

 

 

메서드에 데코레이터 사용하기

클래스를 만들면서 메서드에 데코레이터를 사용할 때는 self를 주의해야 합니다. 인스턴스 메서드는 항상 self를 받으므로 데코레이터를 만들 때도 wrapper 함수의 첫 번째 매개변수는 self로 지정해야 합니다(클래스 메서드는 cls). 마찬가지로 func를 호출할 때도 self와 매개변수를 그대로 넣어야 합니다.

 

def trace(func):
    def wrapper(self, a,b):
        r = func(self, a,b)
        print('{0}(a={1}, b={2}) -> {3}'.format(func.__name__, a, b, r))
        return r
    return wrapper

class Cal:
    @trace
    def add(self, a,b):
        return a+b

c = Cal()
print(c.add(3,5))

-------------------------------------

add(a=3, b=5) -> 8
8

 

 

데코레이터에 값(매개 변수)을 전달할때

 

import functools
def is_multiple(x):
    def real_decorator(func):
        @functools.wraps(func)
        def wrapper(a,b):
            r = func(a,b)
            if r%x==0:
                print('{0}의 반환값은 {1}의 배수입니다.'.format(func.__name__, x))
            else:
                print('{0}의 반환값은 {1}의 배수가 아닙니다.'.format(func.__name__, x))
            return r
        return wrapper
    return real_decorator



@is_multiple(3)     # @데코레이터(인수)
@is_multiple(7)     # @데코레이터(인수)
def add(a, b):
    return a + b

print(add(10, 20))
print(add(2, 5))

-------------------------------------

add의 반환값은 7의 배수가 아닙니다.
add의 반환값은 3의 배수입니다.
30
add의 반환값은 7의 배수입니다.
add의 반환값은 3의 배수가 아닙니다.
7

 

 

클래스로 데코레이터 만들기

 

class trace:
    def __init__(self, func):
        self.func = func

    def __call__(self):
        print('{0}, {1}'.format(self.func.__name__, 'start'))
        self.func()
        print('{0}, {1}'.format(self.func.__name__, 'end'))

@trace
def hello():
    print('hello~~~')

hello()

-------------------------------------

hello, start
hello~~~
hello, end

 

 

클래스로 만든 데코레이터에 매개변수 및 반환값 처리 - 가변 인수 샘플

 

class Trace:
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kargs):
        r = self.func(*args, **kargs)
        print('{0}(args={1}, kargs={2}) ==> {3}'.format(self.func.__name__, args, kargs, r))
        return r
@Trace
def add(a, b):
    return a+b

print(add(1,2))
print(add(a=10, b=20))

-------------------------------------

add(args=(1, 2), kargs={}) ==> 3
3
add(args=(), kargs={'a': 10, 'b': 20}) ==> 30
30

 

 

클래스에 매개변수를 전달하는 데코레이터

 

class Trace:
    def __init__(self, x):
        self.x = x

    def __call__(self, func):
        def wrapper(*args, **kargs):
            r = func(*args, **kargs)
            if r%self.x==0:
                print('{0} 의 반환값은 {1}의 배수입니다'.format(func.__name__, self.x))
                print('{0}(args={1}, kargs={2}) ==> {3}'.format(func.__name__, args, kargs, r))
            else:
                print('{0} 의 반환값은 {1}의 배수가 아닙니다'.format(func.__name__, self.x))
                print('{0}(args={1}, kargs={2}) ==> {3}'.format(func.__name__, args, kargs, r))
            return r
        return wrapper
@Trace(3)
def add(a,b):
    return a+b

print(add(10,20))
print(add(2,5))
print(add(a=2,b=5))

-------------------------------------

add 의 반환값은 3의 배수입니다
add(args=(10, 20), kargs={}) ==> 30
30
add 의 반환값은 3의 배수가 아닙니다
add(args=(2, 5), kargs={}) ==> 7
7
add 의 반환값은 3의 배수가 아닙니다
add(args=(), kargs={'a': 2, 'b': 5}) ==> 7
7

'Python > Intermediate' 카테고리의 다른 글

[Python] 병행성(Concurrency) - generator  (0) 2021.05.25
[Python] 병행성(Concurrency) - basic  (0) 2021.05.25
[Python] Closure  (0) 2021.05.25
[Python] 일급함수  (0) 2021.05.24
[Python] 고급 - Dict 및 Set(2)  (0) 2021.05.24