파이썬 데코레이터(Decorator) 5가지 소개

Python은 간결한 문법과 데이터 분석, 웹 개발 등 다양한 분야에서 강력한 응용 프로그램을 지원하는 덕분에 좋아하는 프로그래밍 언어입니다.

하지만 오랜 기간 코드를 작성하면서도 데코레이터 기능을 거의 사용하지 않았습니다. 클래스의 정적 메서드를 데코레이트하기 위한 @staticmethod를 사용하는 경우를 제외하면 말이죠.

그래서 이번 글에서는 데코레이터의 개념을 깊이 탐구하고, 파이썬 코드의 효율성을 높일 수 있는 실용적인 데코레이터 5가지를 소개하고자 합니다.

데코레이터(Decorator)란 무엇인가?

데코레이터는 파이썬의 강력한 언어 기능으로, 원래의 함수 코드를 수정하지 않고도 동적으로 기능을 추가하거나 함수의 동작을 수정할 수 있습니다. 데코레이터는 기본적으로 다른 함수나 클래스를 인자로 받아 새로운 함수나 클래스를 반환하는 함수입니다.

데코레이터는 함수를 확장하거나 감싸는 간결하고 우아한 방법을 제공하여 코드의 가독성과 유지보수성을 향상시킵니다.

주로 다음과 같은 상황에서 데코레이터가 사용됩니다:

  • 로깅, 성능 분석, 입력 유효성 검사 등 추가적인 기능이나 로직을 추가할 때.
  • 캐시 결과, 재시도 메커니즘 추가 등 함수의 동작을 수정할 때.
  • 주요 비즈니스 로직에서 공통적으로 발생하는 문제를 분리할 때.

이제 간단하지만 유용한 5가지 데코레이터를 알아보겠습니다.

01. timer: 함수의 실행 시간 측정

코드 성능 최적화는 매우 중요합니다. 이 데코레이터는 특정 함수의 실행 시간을 추적하고 경과 시간을 출력합니다. 이 데코레이터를 사용하여 코드의 병목 지점을 쉽게 파악하고 중요한 부분을 최적화할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
import time

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

데코레이터를 사용하려면 원하는 함수 위에 @ 기호를 사용해 적용하면 됩니다.

1
2
3
4
5
6
7
@timer
def train_model():
print("Starting the model training function...")
time.sleep(5)
print("Model training completed!")

train_model()
1
2
3
4
# 출력:
Starting the model training function
Model Training completed!
Execution time: 5.006425619125366 seconds

02. debugger: 디버깅을 쉽게

각 함수의 입력과 출력을 출력하여 디버깅을 더 쉽게 할 수 있는 래퍼 함수를 만들 수 있습니다. 이 방법은 여러 개의 print 문을 사용하지 않고도 함수의 실행 흐름을 쉽게 파악할 수 있습니다.

1
2
3
4
5
6
7
def debugger(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__} with args: {args} kwargs: {kwargs}")
result = func(*args, **kwargs)
print(f"{func.__name__} returned: {result}")
return result
return wrapper
1
2
3
4
5
@debugger
def add_numbers(x, y):
return x + y

add_numbers(7, y=5)
1
2
3
# 출력:
Calling add_numbers with args: (7,) kwargs: {'y': 5}
add_numbers returned: 12

03. memoize: 결과 캐싱

코드에서 일부 반복 실행되는 부분은 큰 컴퓨팅 자원을 소모할 수 있습니다. 이 경우 memoize 데코레이터를 사용하여 함수 호출 결과를 캐싱할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
def memoize(func):
cache = {}
def wrapper(*args):
if args in cache:
return cache[args]
else:
result = func(*args)
cache[args] = result
return result
return wrapper

피보나치 수 계산 함수에 캐싱을 적용하면, 동일한 입력에 대해 함수가 한 번만 실행되고 이후에는 캐시된 결과를 사용하게 됩니다.

1
2
3
4
5
6
@memoize
def fibonacci(n):
if n <= 1:
return n
else:
return fibonacci(n-1) + fibonacci(n-2)

캐싱을 사용하지 않은 경우와 비교해 보면, 캐싱된 버전은 몇 밀리초밖에 걸리지 않는 반면, 캐싱되지 않은 버전은 거의 1분이 걸릴 수 있습니다.


04. retry: 재시도 로직

데이터 과학과 소프트웨어 개발에서 외부 시스템에 의존하는 경우가 많지만, 모든 외부 시스템이 안정적이지는 않습니다. 예기치 않은 오류가 발생했을 때, 코드가 잠시 기다렸다가 다시 시도하도록 할 수 있습니다. 이 재시도 로직을 데코레이터로 구현하여 쉽게 적용할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import time

def retry(max_attempts, delay=1):
def decorator(func):
def wrapper(*args, **kwargs):
attempts = 0
while attempts < max_attempts:
try:
return func(*args, **kwargs)
except Exception as e:
attempts += 1
print(f"Attempt {attempts} failed: {e}")
time.sleep(delay)
print(f"Function failed after {max_attempts} attempts")
return wrapper
return decorator
1
2
3
4
5
6
@retry(max_attempts=3, delay=2)
def fetch_data(url):
print("Fetching the data..")
raise TimeoutError("Server is not responding.")

fetch_data("https://example.com/data")

이 코드는 최대 3번까지 2초 간격으로 재시도합니다.

05. exception_handler: 예외 처리 간소화

exception_handler 데코레이터는 함수에서 발생하는 예외를 잡아 처리할 수 있습니다. 필요에 따라 로깅하거나 추가적인 오류 처리를 수행할 수 있습니다.

1
2
3
4
5
6
7
def exception_handler(func):
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception as e:
print(f"An exception occurred: {str(e)}")
return wrapper
1
2
3
4
5
6
@exception_handler
def divide(x, y):
result = x / y
return result

divide(10, 0)
1
2
# 출력:
An exception occurred: division by zero

이 데코레이터는 코드를 단순화하고 예외 처리와 오류 로깅을 위한 일관된 절차를 설정하는 데 매우 유용합니다.

요약

데코레이터는 함수에 새로운 동작을 적용하는 매우 편리한 방법입니다. 데코레이터를 사용하면 복잡한 작업을 간소화하고 코드 가독성을 높이며, 생산성을 향상시킬 수 있습니다.

데코레이터는 파이썬의 강력한 기능 중 하나이며, 적절히 활용하면 코드의 유지보수성과 확장성을 높일 수 있습니다. 데코레이터를 사용해 여러분의 파이썬 코드를 한 단계 더 발전시켜보세요. 😊

Share