[Python] 데코레이터를 활용한 효과적인 에러 핸들링 패턴
1. 에러 핸들링의 중요성 ** 소프트웨어 개발에서 에러 핸들링은 필수적인 요소입니다.
하지만 많은 경우,
같은 방식의 에러 핸들링 코드가 반복되면서 코드 중복이 발생하고 유지보수가 어려워집니다.
파이썬에서는 데코레이터(Decorator) 를 활용하여 이러한 문제를 해결할 수 있습니다.
데코레이터를 사용하면 에러 핸들링을 중앙화하여 가독성과 유지보수성을 향상시킬 수 있습니다.
이 글에서는 데코레이터를 활용한 에러 핸들링 패턴을 설명하고,
실전에서 활용할 수 있는 다양한 예제와 모범 사례를 소개하겠습니다.
2. 데코레이터란? ** 데코레이터는 함수나 메서드의 기능을 확장하는 파이썬의 강력한 기능입니다.
특정 함수에 추가적인 기능(로깅, 성능 측정, 에러 핸들링 등)을 쉽게 부여할 수 있습니다.
기본적인 데코레이터 구조
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import functools
def my_decorator(func):
@functools.wraps(func) # 원본 함수의 메타데이터 유지
def wrapper(*args, **kwargs):
print("함수가 실행되기 전입니다.")
result = func(*args, **kwargs)
print("함수가 실행된 후입니다.")
return result
return wrapper
@my_decorator
def my_function():
print("실제 함수 실행")
my_function()
3. 에러 핸들링을 위한 데코레이터 패턴 에러 핸들링은 데코레이터를 적용하기 좋은 대표적인 영역 중 하나입니다.
여러 함수에서 반복적으로 사용되는 try-except 블록을 데코레이터로 감싸면,
코드의 일관성과 유지보수성이 향상됩니다.
기본적인 에러 핸들링 데코레이터
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import functools
import logging
# 로깅 설정
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def handle_exceptions(func):
"""예외를 처리하는 데코레이터"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception as e:
logger.error(f"{func.__name__} 실행 중 오류 발생: {str(e)}")
return None # 기본값 반환
return wrapper
@handle_exceptions
def divide(a, b):
return a / b
# 사용 예시
print(divide(10, 2)) # 정상 실행
print(divide(10, 0)) # 오류 발생 → None 반환
4. 고급 에러 핸들링 데코레이터 실제 프로젝트에서는 보다 복잡한 예외 처리가 필요합니다.
예를 들어,
재시도(자동 재시도),
특정 예외 구분,
사용자 정의 예외 처리 기능이 포함될 수 있습니다.
고급 에러 핸들링 데코레이터
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import functools
import logging
import time
# 로깅 설정
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class ErrorHandler:
def __init__(self, retry_count=0, retry_delay=1.0, critical_exceptions=None, default_value=None):
self.retry_count = retry_count
self.retry_delay = retry_delay
self.critical_exceptions = critical_exceptions or []
self.default_value = default_value
def __call__(self, func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
attempt = 0
while attempt self.retry_count:
logger.error(f"{func.__name__} 최대 재시도 횟수 초과. 오류: {e}")
return self.default_value
logger.warning(f"{func.__name__} 실행 중 오류 발생: {e}. {attempt}번째 재시도 중...")
time.sleep(self.retry_delay)
return wrapper
# 사용 예시
@ErrorHandler(retry_count=3, retry_delay=2.0, default_value="오류 발생")
def unstable_function():
import random
if random.random() < 0.7:
raise ValueError("랜덤 오류 발생!")
return "성공"
print(unstable_function()) # 실패할 경우 3번 재시도 후 "오류 발생" 반환
5. 실전 활용: HTTP 요청에 적용 HTTP 요청 시 네트워크 오류가 발생할 수 있습니다.
이를 해결하기 위해,
지수 백오프(exponential backoff) 전략을 사용한 에러 핸들링 데코레이터를 적용할 수 있습니다.
HTTP 요청용 에러 핸들링 데코레이터
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import functools
import requests
import logging
import time
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def http_error_handler(retry_count=3, retry_delay=1.0):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(retry_count + 1):
try:
response = func(*args, **kwargs)
if response.status_code == 200:
return response
if response.status_code == 429:
logger.warning("API 할당량 초과, 대기 후 재시도")
time.sleep(retry_delay * (2 ** attempt))
elif attempt < retry_count:
logger.warning(f"요청 실패: {response.status_code}, {attempt+1}번째 재시도 중...")
time.sleep(retry_delay)
else:
logger.error(f"최대 재시도 횟수 초과. 마지막 상태 코드: {response.status_code}")
return response
except requests.RequestException as e:
if attempt < retry_count:
time.sleep(retry_delay * (2 ** attempt))
else:
logger.error(f"네트워크 오류 발생: {e}")
raise
return wrapper
return decorator
@http_error_handler(retry_count=3, retry_delay=2)
def fetch_data(url):
return requests.get(url, timeout=5.0)
# 사용 예시
try:
response = fetch_data("https://api.example.com/data")
print(response.json())
except Exception as e:
print(f"데이터 요청 실패: {e}")
6. 데코레이터를 활용한 에러 핸들링의 장점
코드 중복 감소: 동일한 에러 처리 로직을 여러 곳에 반복하지 않아도 됨
가독성 향상: 핵심 로직이 try-except 블록으로 가려지지 않음
유지보수성 증가: 에러 처리 로직을 변경할 때 한 곳만 수정하면 됨
일관성 유지: 모든 함수가 동일한 방식으로 예외를 처리
7. 결론
데코레이터를 활용한 에러 핸들링은 코드를 깔끔하게 정리하고 유지보수성을 높이는 효과적인 방법입니다.
실전에서 활용할 수 있도록,
기본적인 예외 처리부터 재시도 및 HTTP 요청까지 다양한 데코레이터 패턴을 소개했습니다.

