betterway26데코레이터

김승환·2021년 7월 11일

코딩의 기술

목록 보기
17/36

functools.wrap을 사용해 함수 데코레이터를 정의하라

파이썬은 함수에 적용할 수 있는 데코레이터를 정의하는 특별한 구문을 제공한다.

데코레이터는

  • 호출되기 전과 후에 코드를 추가로 실행 해준다.
  • 데코레이터가 자신이 감싸고 있는 함수의 입력 인자, 반환 값, 함수에서 발생한 오류에 접근할 수 있다는 뜻이다.
  • 함수의 의미를 강화하거나 디버깅을 하거나 함수를 등록하는 등의 일에 이런 기능을 유용하게 쓸 수 있다.
#ex) 함수가 호출될 때마다 인자 값과 반환 값을 출력하고 싶다고 하자.
# 이런 기능은 재귀적 함수에서 
#함수 호출이 재귀적으로 내포되는 경우 디버깅할 때 특히 유용하다.

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

이 데코레이터를 함수에 적용할 때는 @기호를 사용

@trace
def fibonacci(n):
    """Return n 번째 피보나치 수"""
    if n in (0, 1):
        return n
    return (fibonacci(n - 2) + fibonacci(n - 1))

@ 기호를 사용하는 것은 이 함수에 대해 데코레이터를 호출한 후, 데코레이터가 반환한 결과*를 원래 함수가 속해야 하는 영역에 원래 함수와 같은 이름으로 등록하는 것과 같다.

#wrapper의 코드를 원래의 fibonacci함수가 실행되기 전과 후에 실행한다.
# wrapper는 재귀 스택의 매 단계마다 함수의 인자와 반환 값을 출력한다.
fibonacci = trace(fibonacci)
fibonacci(4)

fibonacci((0,), {}) -> 0
wrapper((0,), {}) -> 0
wrapper((0,), {}) -> 0
fibonacci((1,), {}) -> 1
wrapper((1,), {}) -> 1
wrapper((1,), {}) -> 1
fibonacci((2,), {}) -> 1
wrapper((2,), {}) -> 1
wrapper((2,), {}) -> 1
fibonacci((1,), {}) -> 1
wrapper((1,), {}) -> 1
wrapper((1,), {}) -> 1
fibonacci((0,), {}) -> 0
wrapper((0,), {}) -> 0
wrapper((0,), {}) -> 0
fibonacci((1,), {}) -> 1
wrapper((1,), {}) -> 1
wrapper((1,), {}) -> 1
fibonacci((2,), {}) -> 1
wrapper((2,), {}) -> 1
wrapper((2,), {}) -> 1
fibonacci((3,), {}) -> 2
wrapper((3,), {}) -> 2
wrapper((3,), {}) -> 2
fibonacci((4,), {}) -> 3
wrapper((4,), {}) -> 3
wrapper((4,), {}) -> 3
3

데코레이터 @ 예시

def decorator(n):
    def outer(func):
        def deco_func():
            for i in range(n):
                print("tistory")
            func()
        return deco_func
    return outer

def function1():
    print("ssungkang")
#기존엔 변수에 함수를 쓰워줬다.
decorator_3 = decorator(3)
function1 = decorator_3(function1)

function1()	

tistory
tistory
tistory
ssungkang

def decorator(n):
    def outer(func):
        def deco_func():
            for i in range(n):
                print("tistory")
            func()
        return deco_func
    return outer

@decorator(3)
def function1():
    print("ssungkang")

    
#안에 종속하게할 함수위에 @를 이용하여 적용해주면 된다.
function1()

tistory
tistory
tistory
ssungkang

  • 위 코드는 잘 작동하지만 부작용이 있다.
  • 데코레이터가 반환하는 함수의 이름이 fibonacci가 아니게 된다.
print(fibonacci)

<function trace..wrapper at 0x0000013F5F284048>

  • trac 함수는 wrapper함수를 반환하고 값이 fibonacci라는 이름으로 등록된다.
  • 이런 동작은 디버거와 같이 인트로스펙션을하는 도구에서 문제가 된다.
# 예를들면 꾸며진 fibonacci함수에 help 내장 함수를 호출하면 쓸모가 없다.
# fibonacci 함수의 맨 앞부분에 있는 독스트링이 출력돼야 하지만, 실제론 아니다,
help(fibonacci)

Help on function wrapper in module main:

wrapper(*args, **kwargs)

#데코레이터가 감싸고 있는 원래 함수의 위치를 찾을 수 없기 때문에 객체직렬화도 깨진다.

import pickle
print(pickle.dumps(fibonacci))

AttributeError Traceback (most recent call last)
in
2
3 import pickle
----> 4 print(pickle.dumps(fibonacci))

AttributeError: Can't pickle local object 'trace..wrapper'

문제를 해결하는 방법은

  • functools 내장 모듈에 정의된 wraps 도우미 함수를 사용하는 것
  • wraps를 wrapper 함수에 적용하면 wraps가 데코레이터 내부에 들어가는 함수에서 중요한 메타데이터를 복사해 wrapper 함수에 적용해준다.
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


@trace
def fibonacci(n):
    """Return n 번째 피보나치 수"""
    if n in (0, 1):
        return n
    return (fibonacci(n - 2) + fibonacci(n - 1))

help(fibonacci)

import pickle
print(pickle.dumps(fibonacci))

Help on function fibonacci in module main:

fibonacci(n)
Return n 번째 피보나치 수

b'\x80\x03cmain\nfibonacci\nq\x00.'

profile
인공지능 파이팅!

0개의 댓글