Home [Better Way #26] functools.wraps을 사용해 함수 데코레이터를 정의하라
Post
Cancel

[Better Way #26] functools.wraps을 사용해 함수 데코레이터를 정의하라

본문은 “파이썬 코딩의 기술 (Effective Python, 2판)”“Chapter 03. Functions”을 읽고 정리한 내용입니다.


데코레이터 (decorator)

  • 자신이 감싸고 있는 함수가 호출되기 전과 후에 코드를 추가로 실행해준다.
    • 즉, 자신이 감싸고 있는 함수의 입력 인자, 반환 값, 함수에서 발생한 오류에 접근할 수 있다.
    • ex) 재귀 함수를 디버깅할 때 유용하다.
  • 데코레이터를 함수에 적용할 때는 @ 기호를 사용한다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    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):
        if n in (0, 1):
            return n
        return (fibonacci(n - 2) + fibonacci(n - 1))
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    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
    # 3
    
    • trace 함수는 wrapper 함수를 반환한다.
    • 데코레이터로 사용되면 반환된 wrapper 함수가 모듈에 fibonacci라는 이름으로 등록되게 된다.

      1
      
      fibonacci = trace(fibonacci)
      
    • 기존 함수와 이름이 동일하지만 새롭게 decorated 된 함수(fibonacci)는 wrapper의 코드를 원래의 함수가 실행되기 전과 후에 실행한다.
  • 데코레이터가 감싸고 있는 원래 함수의 위치를 찾을 수 없기 때문에 다음의 문제가 발생한다.
    1. 인트로스펙션(introspection)을 수행하는 도구(ex. 디버거)가 잘못 동작할 수 있다. (ex. help(fibonacci) → 독스트링이 출력되지 않는다.)
    2. pickle 객체 직렬화가 깨지게 된다.


functools.wraps 함수

  • 데코레이터 작성을 돕는 데코레이터이다.
  • wrapswrapper 함수에 적용하면, 데코레이터 내부에 들어가는 함수에서 중요한 메타데이터를 복사하여 wrapper 함수에 적용해준다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from functools import wraps

def trace(func):
    @wraps(func) # -- functools.wraps 사용
    def wrapper(*args, **kwargs):
        ...
    return wrapper

@trace
def fibonacci(n):
    """n번째 피보나치 수를 반환한다."""
    if n in (0, 1):
        return n
    return (fibonacci(n - 2) + fibonacci(n - 1))
1
2
3
4
5
6
7
8
9
10
11
# (1) docstring이 출력됨
help(fibonacci)
# Help on function fibonacci in module __main__:
# 
# fibonacci(n)
#     n번째 피보나치 수를 반환한다.

# (2) pickle 객체 직렬화가 제대로 동작함
import pickle
print(pickle.dumps(fibonacci))
# b'\x80\x04\x95\x1a\x00\x00\x00\x00\x00\x00\x00\x8c\x08__main__\x94\x8c\tfibonacci\x94\x93\x94.'
This post is licensed under CC BY 4.0 by the author.

[Better Way #25] 위치로만 인자를 지정하게 하거나 키워드로만 인자를 지정하게 해서 함수 호출을 명확하게 만들라

[Better Way #27] map과 filter 대신 컴프리헨션을 사용하라