프로그램을 작성하다 보면 다양한 예외 상황이 발생할 수 있다.
이를 적절하게 처리하면 예기치 않은 프로그램 종료를 방지하고, 디버깅 및 유지보수를 용이하게 할 수 있다.
이번 포스팅에서는 파이썬의 기본 예외 처리 방법과 함께 나만의 커스텀 예외 클래스를 정의하고 활용하는 방법을 살펴보자.
1. 기본 예외 처리
파이썬에서 try, except, else, finally 구문을 이용해 예외를 처리할 수 있다.
- try: 예외가 발생할 수 있는 코드를 작성
- except: 특정 예외가 발생했을 때 이를 처리하는 코드를 작성
- else: 예외가 발생하지 않았을 때 실행할 코드를 작성
- finally: 예외 발생 여부와 상관없이 항상 실행되는 코드를 작성
def divide(a, b):
try:
result = a / b
except ZeroDivisionError as e:
print("0으로 나눌 수 없습니다:", e)
else:
print("결과는:", result)
finally:
print("나눗셈 시도 완료.")
divide(10, 2)
divide(10, 0)

2. 여러 예외 처리
하나의 try 블록에서 여러 종류의 예외를 처리할 수 있다.
예를 들어, 잘못된 타입이나 0으로 나누는 경우를 동시에 처리할 수 있다.
def process_data(data):
try:
# 데이터가 숫자인지 확인, 숫자가 아니라면 ValueError 발생
if not isinstance(data, (int, float)):
raise ValueError("데이터가 숫자가 아닙니다.")
result = 100 / data
except ZeroDivisionError:
print("데이터가 0입니다. 0으로 나눌 수 없습니다.")
except ValueError as ve:
print("ValueError 발생:", ve)
else:
print("연산 결과:", result)
finally:
print("데이터 처리 완료.")
process_data(25)
process_data(0)
process_data("문자열")

3. 커스텀 예외 만들기
내장된 예외 외에도, 특정 상황에 맞게 나만의 예외 클래스를 만들어 사용할 수 있다.
커스텀 예외를 정의할 때는 보통 Exception 클래스를 상속받아 만든다.
# 커스텀 예외 클래스 정의
class NegativeValueError(Exception):
"""음수 값을 허용하지 않는 예외입니다."""
def __init__(self, value, message="음수 값은 허용되지 않습니다."):
self.value = value
self.message = message
super().__init__(self.message)
def process_number(n):
if n < 0:
raise NegativeValueError(n)
else:
print("양수 값:", n)
try:
process_number(-10)
except NegativeValueError as ne:
print(f"예외 발생: {ne} (입력값: {ne.value})")

4. 추가 속성을 가진 커스텀 예외
커스텀 예외 클래스는 상황에 따라 추가적인 속성이나 메서드를 가질 수 있다.
예를 들어, 금융 애플리케이션에서 잔액 부족 상황을 나타내는 예외를 정의할 수 있다.
class InsufficientBalanceError(Exception):
"""잔액이 부족할 때 발생하는 예외입니다."""
def __init__(self, balance, amount, message="잔액이 부족합니다."):
self.balance = balance
self.amount = amount
self.message = message
super().__init__(f"{message} (잔액: {balance}, 출금액: {amount})")
def withdraw(balance, amount):
if amount > balance:
raise InsufficientBalanceError(balance, amount)
else:
balance -= amount
print(f"출금 성공! 남은 잔액: {balance}")
return balance
try:
withdraw(100, 150)
except InsufficientBalanceError as ibe:
print("예외 발생:", ibe)

3번 주제의 커스텀 예외와 4번 주제의 차이점에 의문 부호가 찍힐 수 있다.
간단히 차이점을 정리하면:
- 3. 커스텀 예외 만들기
기본적으로>Exception클래스를 상속받아 자신만의 예외를 만드는 방법을 설명한 것이다.
이 방법은 예외 상황을 보다 명확하게 구분하고, 필요한 경우 에러 메시지를 커스터마이징할 수 있게 해준다. - 4. 추가 속성을 가진 커스텀 예외
기본 커스텀 예외에 더해, 예외 발생 시 추가적인 정보를 함께 담을 수 있도록 속성(예: 잔액, 출금액 등)을 정의하는 방법을 보여준다.
이를 통해 예외 처리 시 더 상세한 컨텍스트를 제공할 수 있다.
즉, 3번은 커스텀 예외의 기본 정의에 초점을 맞추고, 4번은 예외 객체에 추가적인 데이터를 포함시키는 방법을 다룬 것이다.
파이썬의 예외 처리는 기본 try, except 구문 외에도 여러 부가적인 기능과 기법이 있다.
이제부턴 조금 더 심화된 예외 처리에 대해 알아본다.
5. 예외 체이닝 (Exception Chaining)
어떤 예외가 발생했을 때, 다른 예외를 발생시키면서 원래 예외를 함께 연결할 수 있다.
이렇게 하면, 문제의 근본 원인을 추적하기가 더 쉽다.
raise NewException(...) from original_exception 구문으로 사용한다.
def read_file(file_path):
try:
with open(file_path, 'r') as f:
return f.read()
except FileNotFoundError as e:
raise RuntimeError("파일을 읽는 중 오류 발생") from e
try:
read_file("non_existent_file.txt")
except RuntimeError as re:
print("에러 발생:", re)

6. 로깅과 예외 처리
예외 발생 시 단순 출력 대신 logging 모듈을 활용하여 로그 파일에 기록하거나, 시스템 모니터링 도구와 연동할 수 있다.
logging.error()나 logging.exception() 함수를 사용한다.
import logging
logging.basicConfig(level=logging.ERROR, filename="app.log")
def divide(a, b):
try:
return a / b
except ZeroDivisionError as e:
logging.error("제로로 나누기 시도", exc_info=True)
return None
divide(10, 0)

코드를 돌리면 에러는 발생하지 않는다.
하지만 app.log 파일이 작성되고 안의 내용은 다음과 같다.
ERROR:root:제로로 나누기 시도
Traceback (most recent call last):
File "/var/folders/n1/c_8tdctx61576flx4bc__z9r0000gn/T/ipykernel_90750/1555983670.py", line 7, in divide
return a / b
~~^~~
ZeroDivisionError: division by zero
7. Context Manager와 예외 처리
with문을 사용하는 context manager는 리소스를 열고 닫는 과정을 자동화하여, 예외가 발생하더라도 안전하게 리소스를 정리할 수 있게 해준다.
class ManagedFile:
def __init__(self, filename):
self.filename = filename
def __enter__(self):
self.file = open(self.filename, 'r')
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
if self.file:
self.file.close()
# 예외 발생 시 False를 반환하면 예외가 전파됩니다.
return False
try:
with ManagedFile("test.txt") as f:
content = f.read()
print(content)
except Exception as e:
print("파일 처리 중 오류:", e)

8. 특정 예외만 잡기
가능한 한 구체적인 예외만 처리하는 것이 좋다.
이렇게 하면 의도치 않은 예외를 잡아내어 디버깅을 어렵게 만드는 일을 방지할 수 있다.
try:
# 실행할 코드
pass
except ValueError:
print("ValueError 처리")
except (TypeError, KeyError) as e:
print(f"다른 특정 예외 처리: {e}")
9. 커스텀 예외에 추가 메서드 정의
커스텀 예외 클래스에 추가적인 속성이나 메서드를 정의하면, 예외가 발생했을 때 관련 정보를 좀 더 쉽게 제공할 수 있다.
class DetailedError(Exception):
def __init__(self, message, code):
super().__init__(message)
self.code = code
def log_error(self):
print(f"오류 코드 {self.code}: {self}")
try:
raise DetailedError("상세한 오류 발생", 404)
except DetailedError as de:
de.log_error()

이와 같이 파이썬의 예외 처리는 기본적인 try, except 구문을 포함, 예외 체이닝이나 로깅, context manager를 통한 리소스 정리, 특정 예외만 처리, 커스텀 예외에 추가 메서드를 포함시키는 방법 등으로 강력하고 유지보수하기 쉬운 코드를 작성할 수 있다.
'머신 러닝 > Python' 카테고리의 다른 글
| [Python] 파이썬 데코레이터(Decorator) 설명과 활용 예시 (0) | 2025.02.05 |
|---|---|
| [Python] matplotlib으로 서브플롯 그리고 각각에 타이틀 추가하기 (0) | 2025.01.27 |
| [Python] npz 파일 키 확인하는 법 (0) | 2024.08.04 |
| [Python] 파이썬에서 숫자에 콤마(,)찍어서 출력하는 방법 (0) | 2024.07.14 |
| [Python] for문을 사용하지 않은 List Comprehension 예제 (1) | 2024.07.12 |