Python: Decorator ?

Decorator 는 말 그대로, 다른 함수에 살짝 장식(양념?)을 하는 개념이라 생각하는게 좋겠다. 제대로 써본 적이 없어서 지금 단계에선 뭐라 말하기가 어렵지만..

책들 설명에 따르면, 주로 버그를 찾을 용도(Debugging)로 사용된다고 한다.
Introducing Python(1st Ed.) p99 에는 이렇게 설명이 나와있다.

A decorator is a function that takes one function as input and returns another function.

한 함수를 받아, 부가 기능을 추가해서 다른 함수로 변환(사실은 반환)해주는 기능. 이게 Decorator 의 역할이다.
아래 예는 Effective Python(2nd Ed.) p103 에서 가져왔다.
또, 예제에 있는 !rrepr가리킨다.

def trace(func):
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        print(f'{func.__name__}({args!r}, {kwargs!r}) '
              f'-> {result!r}')
        return result
    return wrapper

def fibonacci(n):
    """Return the n-th Fibonacci number"""
    if n in (0, 1):
        return n
    return (fibonacci(n - 2) + fibonacci(n - 1))

fibonacci 가 원 함수고, trace 가 decorator 용이다. fibonacci 를 데코레이터로 사용하려면 다음 처럼 할당해주면 된다.

new_fibonacci = trace(fibonacci)

또, 아예 함수 정의 시에 데코레이터를 붙여버릴 수도 있다.

@trace
def fibonacci(n):
    """Return the n-th Fibonacci number"""
    if n in (0, 1):
        return n
    return (fibonacci(n - 2) + fibonacci(n - 1))

@데코레이터_함수명def 문 위에 써주고, 그냥 원래하는대로 함수를 정의해주면 된다.

사용은 new_fibonacci(4) 또는, @trace 를 사용했다면 그냥 fibonacci(4) 를 쓰면 된다.

***?
그런데, fibonacci 가 재귀함수를 사용해서 그런건지, 수동으로 할당했을 때와 @를 썼을 때 결과가 살짝 다르다.

# 수동 할당 후 결과:
new_fibonacci(4)
fibonacci((4,), {}) 3
Out[10]: 3

# @trace 후 결과
fibonacci(4)
fibonacci((0,), {}) 0
fibonacci((1,), {}) 1
fibonacci((2,), {}) 1
fibonacci((1,), {}) 1
fibonacci((0,), {}) 0
fibonacci((1,), {}) 1
fibonacci((2,), {}) 1
fibonacci((3,), {}) 2
fibonacci((4,), {}) 3
Out[12]: 3

재귀함수라 좀 헷갈리지만, 아무튼 수동도, 자동도 가능하다는 사실을 기억하라.
하지만 보통은 @함수명 으로 자동방식을 사용하는 듯 하다.

한가지 문제점!

한가지는 아니고, 여러가지 문제점이 있을 수 있다고 하지만, 먼저 이렇게 간단하게 확인해볼 수 있다.

print(fibonacci.__name__)
'wrapper'

fibonacci 함수가 wrapper 로 치환(?)된 상황이기 때문에, 그 이름을 출력하면 wrapper 가 나온다. 이런 문제를 해결하려면, functools 에서 wraps 를 불러 사용해야 한다.

from functools import wraps
def trace(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        print(f'{func.__name__}({args!r}, {kwargs!r}) '
              f'-> {result!r}')
        return result
    return wrapper

이러고 나면, fibonacci 는 그 자신의 정체성(?)을 회복하게 된다.

Author: 아무도안

안녕하세요. 글 남겨주셔서 고맙습니다.