728x90

Python FastAPI를 공부하면서 데코레이터를 알게 되었는데 왜 어떻게 작동하는지 잘 이해가 가지 않아서 공부를 하게 되었고 이에 대해 내가 알게된 점에 대해 소개해보려고 한다. 코드는 여기에서 확인할 수 있다.

 

데코레이터란 무엇인가?


데코레이터는 파이썬의 고급 기능 중 하나로, 함수나 메서드에 코드를 수정하지 않고 기능을 주입하는 방법입니다. 함수를 여러 기능으로 사용해야 하며 코드에 수정을 가하지 않을 때 자주 사용한다. 데코레이터는 함수로 인자를 받고, 함수를 감싸는 래퍼(wrapper)를 정의하여, 함수에 추가적인 기능을 할 수 있도록 한다. 

 

데코레이터를 사용하는 이유는?


사용하는데 여러가지 이유가 있다.

  • 코드 재사용 : 코드가 중복될 때 함수를 사용하는데 이때 함수를 여러 방법으로 사용하기 위해 데코레이터를 사용한다.
  • 기능 확장 : 기존 코드에 추가적인 기능을 추가하기 위해서 사용한다.
  • 래퍼 함수에 대한 제어 : 데코레이터 내부의 함수를 제어하기 위해 사용한다.

 

데코레이터의 구조


데코레이터 함수

데코레이터의 함수 구조는 다음과 같다.

def simple_decorator(func):
    def wrapper():
        print("Something is happening before the function is called.")
        func()
        print("Something is happening after the function is called.")
    return wrapper

@simple_decorator
def say_hello():
    print("Hello!")

say_hello()
'''
Something is happening before the function is called.
Hello!
Something is happening after the function is called.
'''

@인자를 사용하여 표현한 데코레이터이다. 함수를 @로 바꾸어 함수를 간단하게 표현한다. 

데코레이터 함수 내부에는 wrapper함수를 써야한다. 이름은 무관하지만 내부에 함수를 정의해야하며, return값은  내부에 정의한 함수를 써야한다.

위의 코드는 데코레이터를 쓰지 않으면 다음과 같이 표현할 수 있다.

def simple_decorator(func):
    def wrapper():
        print("Something is happening before the function is called.")
        func()
        print("Something is happening after the function is called.")
    return wrapper

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

# 데코레이터를 수동으로 적용
decorated_say_hello = simple_decorator(say_hello)

# 함수 호출
decorated_say_hello()
'''
Something is happening before the function is called.
Hello!
Something is happening after the function is called.
'''

 

데코레이터 클래스

데코레이터 클래스 구조는 다음과 같다.

class SimpleDecorator:
    def __init__(self, func):
        self.func = func

    def __call__(self):
        print("Something is happening before the function is called.")
        self.func()
        print("Something is happening after the function is called.")

@SimpleDecorator
def say_hello():
    print("Hello!")

say_hello()
'''
Something is happening before the function is called.
Hello!
Something is happening after the function is called.
'''

클래스가 실행되는 것에 대해 설명은 다음과 같다.

  1. SimpleDecorator 클래스는 __init__ 메서드에서 데코레이터가 감싸야 할 함수를 인자로 받아 저장한다.
  2. __call__ 메서드는 데코레이터가 적용된 함수가 호출될 때 실행된다.
  3. @SimpleDecorator를 사용하여 say_hello 함수에 데코레이터를 적용하면, say_hello 함수가 호출될 때 SimpleDecorator 클래스의 __call__ 메서드가 실행된다.

 

데코레이터 예시


여기서는 함수에 대해 예시 3개에 대해 설명한다.

  • 실행시간 측정
  • 파라미터 전달
  • 디버깅

 

실행 시간 측정

데코레이터 사용

import time

def timer(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"{func.__name__} took {end_time - start_time:.4f} seconds")
        return result
    return wrapper

@timer
def slow_function(delay_time):
    time.sleep(delay_time)

slow_function(1)

'''
slow_function took 1.0007 seconds
'''

데코레이터 없이 사용

import time

def slow_function(delay_time):
    start_time = time.time()
    time.sleep(delay_time)
    end_time = time.time()
    print(f"slow_function took {end_time - start_time:.4f} seconds")
    return "Done"

slow_function(1)
'''
slow_function took 1.0007 seconds
'''

 

파라미터 전달

데코레이터 사용

def repeat(num_times):
    def decorator_repeat(func):
        def wrapper(*args, **kwargs):
            for _ in range(num_times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator_repeat

@repeat(num_times=3)
def greet(name):
    print(f"Hello {name}")

greet("Alice")

데코레이터 없이 사용

def repeat(num_times):
    def decorator_repeat(func):
        def wrapper(*args, **kwargs):
            for _ in range(num_times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator_repeat

def greet(name):
    print(f"Hello {name}")

# 데코레이터를 수동으로 적용
decorated_greet = repeat(num_times=3)(greet)

# 함수 호출
decorated_greet("Alice")

 

디버깅

데코레이터 사용

def debug(func):
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        print(f"{func.__name__} called with {args}, {kwargs} returned {result}")
        return result
    return wrapper

@debug
def make_greeting(name, age=None):
    greeting = f"Hello {name}"
    if age:
        greeting += f", you are {age} years old!"
    return greeting

make_greeting("Alice", age=30)
'''
make_greeting called with ('Alice',), {'age': 30} returned Hello Alice, you are 30 years old!
'''

데코레이터 없이 사용

def make_greeting(name, age=None):
    greeting = f"Hello {name}"
    if age:
        greeting += f", you are {age} years old!"
    print(f"make_greeting called with ({name}, {age}) returned {greeting}")
    return greeting

make_greeting("Alice", age=30)
'''
make_greeting called with ('Alice',), {'age': 30} returned Hello Alice, you are 30 years old!
'''

 

728x90