파이썬을 사용하다 보면 코드를 재사용하고 기능을 확장하는 데 유용한 데코레이터를 자주 만나게 됩니다. 데코레이터는 기존의 함수를 수정하지 않으면서 기능을 추가할 수 있는 매우 강력한 기능으로, 특히 로그 기록, 캐싱, 인증 등의 작업에 유용합니다.
데코레이터는 기본적으로 함수나 클래스를 인자로 받아 새로운 함수를 반환하는 함수입니다. 이 방식으로 특정 기능을 함수에 추가하거나 수정 없이 재사용할 수 있습니다. 데코레이터는 파이썬의 고유 기능 중 하나이며, 코드를 효율적으로 관리하고 모듈화하는 데 매우 유리합니다.
이번 글에서는 파이썬 데코레이터의 기초부터 시작해, 실제 개발에서 활용할 수 있는 다양한 데코레이터 예제를 함께 알아보겠습니다.
1. 기본 데코레이터 예제: 접근 전후 알림 추가하기
함수를 호출하기 전후로 알림을 주는 기본적인 데코레이터입니다. API 호출 전후, 파일 작업 전후 등의 상황에서 쉽게 사용할 수 있습니다.
def notify_execution(func):
def wrapper(*args, **kwargs):
print(f"[INFO] Starting '{func.__name__}' execution.")
result = func(*args, **kwargs)
print(f"[INFO] Finished '{func.__name__}' execution.")
return result
return wrapper
@notify_execution
def process_data(data):
# 데이터 처리 로직
print("Processing data...")
return f"Processed {data}"
# 실제 실행
print(process_data("Sample data"))
설명: notify_execution
데코레이터는 함수의 시작과 끝에 알림을 출력합니다. 주로 데이터 처리나 작업 흐름을 추적하는 데 유용합니다.
2. 입력 데이터 유효성 검사 데코레이터
함수에 전달되는 인자의 유효성을 검사하는 데코레이터입니다. 웹 애플리케이션이나 데이터 처리 파이프라인에서 데이터 검증을 간단히 추가할 수 있습니다.
def validate_positive_args(func):
def wrapper(*args):
# 모든 인자가 양수인지 확인
if not all(isinstance(arg, (int, float)) and arg > 0 for arg in args):
raise ValueError("All arguments must be positive numbers")
return func(*args)
return wrapper
@validate_positive_args
def calculate_volume(length, width, height):
return length * width * height
# 실제 실행
print(calculate_volume(5, 10, 3)) # 정상 실행
# print(calculate_volume(5, -10, 3)) # ValueError 발생
설명: validate_positive_args
는 모든 인자가 양수인지 확인하고, 그렇지 않을 경우 예외를 발생시킵니다. 유효성 검증을 위해 여러 함수에 일관된 검사를 적용할 수 있습니다.
3. 재시도 (Retry) 데코레이터
API 요청과 같은 외부 자원을 사용할 때 일시적인 네트워크 문제나 서버 응답 실패가 발생할 수 있습니다. 이를 대비해 재시도 데코레이터를 사용해보겠습니다.
import time
def retry(retries=3, delay=1):
def decorator(func):
def wrapper(*args, **kwargs):
for attempt in range(1, retries + 1):
try:
return func(*args, **kwargs)
except Exception as e:
print(f"Attempt {attempt} failed: {e}")
time.sleep(delay)
raise Exception("All attempts failed")
return wrapper
return decorator
@retry(retries=3, delay=2)
def fetch_data():
# 외부 API 호출을 시뮬레이션
if time.time() % 2 < 1:
raise ConnectionError("Failed to fetch data")
return "Data fetched successfully"
# 실제 실행
print(fetch_data())
설명: retry
데코레이터는 실패할 가능성이 있는 함수 호출을 특정 횟수만큼 재시도합니다. 예기치 않은 일시적 오류에 대비해 유용하게 사용할 수 있습니다.
4. 캐시 데코레이터 (메모이제이션)
복잡한 계산 결과를 저장해두고, 동일한 인자로 호출될 때 캐싱된 결과를 반환하는 데코레이터입니다. CPU 사용량이 높은 작업에서 성능을 개선할 수 있습니다.
def cache(func):
cached_results = {}
def wrapper(*args):
if args in cached_results:
print(f"[CACHE] Returning cached result for {args}")
return cached_results[args]
result = func(*args)
cached_results[args] = result
print(f"[CACHE] Caching result for {args}")
return result
return wrapper
@cache
def expensive_calculation(x, y):
# 복잡한 계산 시뮬레이션
time.sleep(1)
return x * y
# 실제 실행
print(expensive_calculation(5, 3)) # 캐싱하지 않은 결과 계산
print(expensive_calculation(5, 3)) # 캐싱된 결과 반환
설명: cache
데코레이터는 동일한 인자로 반복 호출되는 함수에서 캐시된 결과를 반환합니다. 계산 속도를 높이고 서버 부하를 줄이는 데 유용합니다.
5. 권한 기반 접근 제어 데코레이터
웹 애플리케이션에서 특정 역할이 있는 사용자만 특정 기능에 접근할 수 있도록 제한할 수 있습니다.
def role_required(role):
def decorator(func):
def wrapper(user, *args, **kwargs):
if user.get("role") != role:
raise PermissionError(f"Access denied: User needs '{role}' role")
return func(user, *args, **kwargs)
return wrapper
return decorator
@role_required("admin")
def delete_content(user, content_id):
print(f"{user['name']} deleted content {content_id}")
# 사용자 데이터
admin_user = {"name": "Alice", "role": "admin"}
regular_user = {"name": "Bob", "role": "user"}
# 실제 실행
delete_content(admin_user, 42) # 정상 접근
# delete_content(regular_user, 42) # PermissionError 발생
설명: role_required
데코레이터는 사용자가 특정 권한이 있을 때만 함수를 호출할 수 있도록 제한합니다. 보안이 중요한 웹 애플리케이션에 유용합니다.
6. 함수 실행 전후 시간 측정 (성능 프로파일링)
복잡한 계산이나 데이터 처리 함수의 성능을 측정하여, 실행 시간이 오래 걸리는지 확인할 수 있습니다.
import time
def profile_execution(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"[PROFILE] Execution time for '{func.__name__}': {end_time - start_time:.4f} seconds")
return result
return wrapper
@profile_execution
def complex_task():
# 연산 시간이 오래 걸리는 작업을 시뮬레이션
time.sleep(2)
return "Task complete"
# 실제 실행
complex_task()
설명: profile_execution
데코레이터는 함수의 실행 시간을 계산해줍니다. 성능 최적화가 필요한 함수에 적용해 느린 작업을 추적할 수 있습니다.
7. 함수의 성공 및 실패 결과 로깅
함수가 성공했는지, 오류가 발생했는지를 로깅하는 데코레이터입니다. 로그를 통해 함수를 모니터링하고 디버깅할 때 유용합니다.
import logging
logging.basicConfig(level=logging.INFO)
def log_result(func):
def wrapper(*args, **kwargs):
try:
result = func(*args, **kwargs)
logging.info(f"Function '{func.__name__}' executed successfully with result: {result}")
return result
except Exception as e:
logging.error(f"Function '{func.__name__}' failed with error: {e}")
raise e
return wrapper
@log_result
def divide(a, b):
return a / b
# 실제 실행
print(divide(10, 2)) # 정상 실행
# print(divide(10, 0)) # ZeroDivisionError 발생 및 로깅
설명: log_result
데코레이터는 함수가 정상적으로 실행되었는지, 오류가 발생했는지 로깅합니다. 로그 기록을 통해 디버깅하고, 오류를 추적할 때 유용합니다.
이렇게 다양한 기능의 데코레이터를 통해 함수의 실행 전후 알림, 인자 검증, 캐싱, 권한 관리, 성능 프로파일링, 실행 결과 로깅 등의 기능을 손쉽게 추가할 수 있습니다. 각각의 데코레이터는 현실적인 상황에서 코드의 효율성과 보안성을 높이며, 디버깅과 유지보수에도 큰 도움을 줍니다.
'개발 > Python 스터디' 카테고리의 다른 글
[Python] `*`와 `**`를 함께 사용하기: 위치 인자와 키워드 인자 활용법 (0) | 2024.11.07 |
---|---|
[Python] `**` 문법으로 복잡한 코드를 간단하게: 딕셔너리 언패킹과 키워드 인자 사용법 (1) | 2024.11.06 |
[Python] `*` 문법 완벽 가이드: 함수 인자, 언패킹, 가변 인자까지 (2) | 2024.11.05 |
[Python] `pdb`와 데코레이터 활용 가이드: 특정 함수만 효율적으로 디버깅하기 (0) | 2024.11.04 |
[python] 아직도 print로 디버깅하시나요? Python pdb 디버거로 효율적인 디버깅 시작하기 (0) | 2024.11.03 |