티스토리 뷰

Python

[Python] 데코레이터 (Decorator)

jsilva 2019. 4. 25. 10:43

이 문서는 파이썬 데코레이터에 대해 간단히 요점정리와 예제를 제공합니다.

 

<간단 개념 정리>

 

데코레이터 (=decorator) :

- 다른함수를 인수로 받으면서 callable 을 리턴하는 callable

- 데커레이터는 데커레이트되는 함수(인수로 받은 callable)에 어떤 처리를 수행한뒤, 인수로 받은 함수를 그대로 리턴하거나 다른 콜러블 객체를 리턴한다.

- 런타임이 아닌 import 타임에 실행된다.

# 간단 데코레이터 예제

def decorate(func):
    def decorated():
        print("데코레이터 실행 전")
        func()
        print("데코레이터 실행 후")
    return decorated
 
@decorate
def target():   
    print('target 함수 실행중임')
 
target()

 

콜러블 (=callable) :

- 이름 뒤에 괄호를 사용해서 실행(call)할 수 있는 것.

- __call__ 함수를 가지고 있는 클래스의 인스턴스

- 메소드

 

nonlocal 키워드 :

함수 A 안에 구현된 함수 내장 함수 B의 지역변수가 함수 A 의 변수(자유 변수)를 명시적으로 참조하고자 할때 사용한다.

아래 간단한 예제를 첨부한다.

### 이력을 유지 안하는 잘 못된 고위 함수
 
def make_averager():
    count = 0
    total = 0
     
    def averager(value):
        count += 1
        total += value
        return total / count
    return averager
 
avg3 = make_averager()
avg3(10)
### 이력을 유지 안하는 이동평균 계산 (nonlocal 사용)
 
def make_averager():
    count = 0
    total = 0
     
    def averager(value):
        nonlocal count, total
        count += 1
        total += value
        return total / count
    return averager
 
avg3 = make_averager()
avg3(10)

 

클로저와 자유변수

자유변수(=free variable) :

- 지역범위에 바인딩되어있지 않은 변수

- 글로벌 변수를 특정 함수 안에서 쓰면 해당 글로벌 변수는 자유변수이다.

 

 

 

클로저 (=closure) :

- 함수를 정의할때 존재하던 자유변수에 대한 바인딩을 유지하는 함수

클로저는 내포된함수(inner function) 안에서만 의미가 있다

- 클로저는 함수 본체에 정의하지 않고 참조하는 비전역(nonlocal) 변수를 포함한 확장 범위를 가진 함수다.

- 즉 클로져는 inner 함수 + 해당 함수에서 참조하는 자유변수(nonlocal 변수) 까지 포함한 패키지를 뜻한다. 

 

 

<데코레이터 예제>

 

### 개선된 clock 데커레이터
 
import time
import functools
 
def clock(func):
    @functools.wraps(func)
    def clocked(*args, **kwargs):
        t0 = time.time()
        result = func(*args, **kwargs)
        elapsed = time.time() - t0
        name = func.__name__
        arg_list = []
         
        if args:
            arg_list.append(','.join(repr(arg) for arg in args))
         
        if kwargs:
            paris = ['%s=%r' % (k, w) for k, w in sorted(kwargs.items())]
            arg_list.append(','.join(paris))
             
        arg_str = ','.join(arg_list)
        print(f'[{elapsed:.8f}] {name}({arg_str}) -> {result}')
        return result
    return clocked
         
     
@clock
def snooze(seconds):
    time.sleep(seconds)
     
@clock
def factorial(n):
    return 1 if n < 2 else n * factorial(n - 1)
 
print('*' * 40, 'Calling snooze(.123)')
snooze(.123)
 
print('*' * 40, 'Calling factorial(6)')
print('6! = ', factorial(6))

※ 파이썬에서 제공해주는 functools.wraps 데코레이터 : functools.wraps 를 사용하지 않은 데커레이터는 데커레이트된 함수의 메타데이터를 자신의 것으로 변경한다. functools.wraps 를 사용하면 데커레이트된 함수의 메타데이터를 복사해서 데커레이트의 내포함수에 복사해주게 되므로, 메타 데이터를 덮어 써서 생기는미묘한 문제들을 방지할 수 있다. 그러니 일반적으로 데커레이터를 만들때는 거의 functools.wraps 를 사용해야 한다고 보면 된다

 

<매개변수(parameter)가 있는 데코레이터 예제>

 

- parameter 가 있는 데코레이터의 경우, 2 depth 의 내장함수를 가진다.

- 데코레이터 팩토리 -> 데코레이터 -> 데코레이트 되는 함수 순으로 랩핑됨.

 

# 매개변수화된 clock() 데커레이터
 
import time
 
DEFAULT_FMT = '[{elapsed:0.8f}s] {name} ({args}) -> {result}'
 
def clock(fmt=DEFAULT_FMT):
    def decorate(func):
        def clocked(*_args):
            print('=====================')
            t0 = time.time()
            _result = func(*_args)
            elapsed = time.time() - t0
            name = func.__name__
            args = ', '.join(repr(arg) for arg in _args)
            result = repr(_result)
            print(fmt.format(**locals()))
            return _result
        return clocked
    return decorate
 
 
if __name__ == '__main__':
    @clock()
    def snooze(seconds):
        time.sleep(seconds)
 
    for i in range(3):
        snooze(.123)
 
=====================
output
=====================
[0.12581611s] snooze (0.123) -> None
=====================
[0.12309694s] snooze (0.123) -> None
=====================
[0.12816191s] snooze (0.123) -> None
 
 
    @clock('{name} ({args}) dt={elapsed:0.3f}s')
    def snooze2(seconds):
        time.sleep(seconds)
         
    for i in range(3):
        snooze2(.123)
 
=====================
snooze2 (0.123) dt=0.125s
=====================
snooze2 (0.123) dt=0.126s
=====================
snooze2 (0.123) dt=0.124s
댓글