Python 데코레이터 완벽 가이드

들어가며

Python에서 데코레이터는 함수의 동작을 런타임에 수정할 수 있는 강력한 기능입니다. 이 글에서는 데코레이터의 기본 개념부터 고급 사용법까지 단계별로 살펴보겠습니다.

1. 중첩 함수

데코레이터를 이해하기 전에, 먼저 함수 내에 정의된 함수(중첩 함수)의 개념을 알아야 합니다. 다음과 같은 상황을 고려해 봅시다:

기존 함수 A가 있을 때, 함수 A의 기능을 변경하지 않으면서 새로운 기능을 추가하고 싶다면 어떻게 해야 할까요?

def original_function():
    print("original result")

# 원래 함수를 변수에 저장
temp = original_function

# 새로운 기능을 하는 함수 정의
def enhanced_function():
    print("additional feature")
    temp()  # 원래 함수 호출

# 원래 변수에 새 함수 대입
original_function = enhanced_function

위 코드에서 볼 수 있듯이, original_function이라는 이름이 가리키는 객체가发生了变化했습니다. 처음에는 원래 함수를 가리키고 있었지만, 마지막 줄之后就变成了 새로운 함수를 가리키게 됩니다.

2. 데코레이터 기본

2.1 단순 데코레이터

데코레이터는 다른 함수를 반환하는 함수입니다. 다음 예제를 살펴보세요:

def decorator(func):
    print("decorator code executed")
    
    def wrapper():
        print("before calling")
        func()
        print("after calling")
    
    return wrapper

@decorator
def say_hello():
    print("hello!")

실행 결과를 확인하면, 데코레이터 코드가 함수 호출 전에 먼저 실행되는 것을 볼 수 있습니다. 이는 @decorator가 적용되는 순간 데코레이터 함수가 실행되기 때문입니다.

실제로 위 코드는 다음과 거의 동일합니다:

def say_hello():
    print("hello!")

say_hello = decorator(say_hello)

2.2 매개변수가 있는 함수 데코레이션

데코레이션할 함수에 매개변수가 있는 경우:

def decorator(func):
    def wrapper(*args, **kwargs):
        print("before execution")
        result = func(*args, **kwargs)
        print("after execution")
        return result
    return wrapper

@decorator
def add_numbers(a, b):
    return a + b

3. 여러 데코레이터

하나의 함수에 여러 데코레이터를 적용할 수 있습니다:

def decorator_one(func):
    def wrapper(*args, **kwargs):
        print("decorator_one - before")
        result = func(*args, **kwargs)
        print("decorator_one - after")
        return result
    return wrapper

def decorator_two(func):
    def wrapper(*args, **kwargs):
        print("decorator_two - before")
        result = func(*args, **kwargs)
        print("decorator_two - after")
        return result
    return wrapper

@decorator_two
@decorator_one
def greet(name):
    print(f"Hello, {name}!")

위 코드에서 @decorator_two @decorator_one는 다음과 같이 동작합니다:

greet = decorator_two(decorator_one(greet))

따라서 실행 순서는:

  1. decorator_two의 wrapper 실행
  2. decorator_one의 wrapper 실행
  3. 원래 greet 함수 실행

4. 매개변수를 받는 데코레이터

데코레이터 자체에 매개변수를 전달해야 하는 경우, 함수 하나를 추가로 감싸야 합니다:

def decorator_with_args(prefix):
    def decorator(func):
        def wrapper(*args, **kwargs):
            print(f"{prefix}: before calling")
            result = func(*args, **kwargs)
            print(f"{prefix}: after calling")
            return result
        return wrapper
    return decorator

@decorator_with_args("INFO")
def calculate(x, y):
    return x * y

위 코드는 다음 코드와 equivalent합니다:

calculate = decorator_with_args("INFO")(calculate)

실행 과정:

  1. decorator_with_args("INFO") 호출 → decorator 함수 반환
  2. 반환된 decorator 함수에 원래 함수 전달
  3. 최종적으로 수정된 함수 반환

5. 완전한 데코레이터 정의

실제 프로덕션 코드에서는 데코레이터의 __name__ 属性을 원래 함수의 이름으로 복원해야 합니다. 그렇지 않으면某些 프레임워크에서 함수 시그니처를 사용하는 경우 오류가 발생할 수 있습니다.

import functools

def proper_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print("before execution")
        result = func(*args, **kwargs)
        print("after execution")
        return result
    return wrapper

functools.wraps는 다음 역할을 합니다:

  • __name__ 속성을 원래 함수 이름으로 복원
  • __doc__ 문서 문자열 보존
  • 다른 함수 메타데이터 유지

functools.wraps 대신 직접 할당도 가능하지만, 권장하지 않습니다:

# 권장하지 않는 방법
def proper_decorator(func):
    def wrapper(*args, **kwargs):
        # 기능 구현
        return func(*args, **kwargs)
    wrapper.__name__ = func.__name__
    return wrapper

정리

Python 데코레이터는 함수와 메서드의 동작을 확장하는 elegant한 방법입니다. 로그 기록, 성능 측정, 권한 검사, 캐싱 등 다양한 분야에서 유용하게 활용됩니다. 특히大型 프레임워크에서는 데코레이터를 기반으로 한 기능들이 많이 사용되므로, 이 개념을 잘 이해해 두는 것이 중요합니다.

태그: python decorators Functions functools wrapper

7월 4일 23:00에 게시됨