Python 예외 처리 | try-except, raise, 커스텀 예외 완벽 정리

Python 예외 처리 | try-except, raise, 커스텀 예외 완벽 정리

이 글의 핵심

Python 예외 처리에 대한 실전 가이드입니다. try-except, raise, 커스텀 예외 완벽 정리 등을 예제와 함께 설명합니다.

들어가며

”에러를 우아하게 처리하기”

예외 처리는 프로그램의 안정성을 높이는 핵심 기술입니다.


1. 기본 예외 처리

try-except

try 블록은 문제가 날 수 있는 줄을 안전 구역 안에 두는 것이고, except비상구로 빠져 나와 사용자에게 알맞은 메시지를 보여 주는 곳입니다. 0으로 나누기·잘못된 입력·없는 파일처럼 예상할 수 있는 실패마다 다른 except 절을 두면 원인 파악이 쉬워집니다.

# 기본 형태
try:
    result = 10 / 0
except ZeroDivisionError:
    print("0으로 나눌 수 없습니다")
    result = None

# 여러 예외 처리
try:
    number = int(input("숫자 입력: "))
    result = 10 / number
except ValueError:
    print("숫자를 입력하세요")
except ZeroDivisionError:
    print("0이 아닌 숫자를 입력하세요")

# 예외 객체 받기
try:
    file = open('없는파일.txt', 'r')
except FileNotFoundError as e:
    print(f"에러: {e}")

2. try-except-else-finally

전체 구조

try:
    # 시도할 코드
    file = open('data.txt', 'r')
    content = file.read()
except FileNotFoundError:
    # 예외 발생 시
    print("파일이 없습니다")
else:
    # 예외 없을 때만 실행
    print(f"파일 읽기 성공: {len(content)}자")
finally:
    # 항상 실행 (파일 닫기 등)
    if 'file' in locals():
        file.close()
    print("작업 완료")

3. 예외 발생시키기 (raise)

기본 raise

def divide(a, b):
    if b == 0:
        raise ValueError("b는 0이 될 수 없습니다")
    return a / b

try:
    result = divide(10, 0)
except ValueError as e:
    print(f"에러: {e}")

예외 재발생

def process_data(data):
    try:
        result = int(data)
    except ValueError:
        print("데이터 변환 실패")
        raise  # 예외 재발생

try:
    process_data("abc")
except ValueError:
    print("상위에서 처리")

4. 커스텀 예외

사용자 정의 예외

class InsufficientBalanceError(Exception):
    """잔액 부족 예외"""
    def __init__(self, balance, amount):
        self.balance = balance
        self.amount = amount
        super().__init__(f"잔액 부족: {balance}원 (필요: {amount}원)")

class BankAccount:
    def __init__(self, owner, balance):
        self.owner = owner
        self.balance = balance
    
    def withdraw(self, amount):
        if amount > self.balance:
            raise InsufficientBalanceError(self.balance, amount)
        self.balance -= amount
        return self.balance

# 사용
account = BankAccount("철수", 10000)

try:
    account.withdraw(15000)
except InsufficientBalanceError as e:
    print(e)  # 잔액 부족: 10000원 (필요: 15000원)
    print(f"현재 잔액: {e.balance}원")

5. 주요 예외 타입

자주 쓰는 예외

# ValueError: 값이 잘못됨
try:
    int("abc")
except ValueError:
    print("숫자 변환 실패")

# TypeError: 타입이 잘못됨
try:
    "hello" + 5
except TypeError:
    print("타입 불일치")

# KeyError: 딕셔너리 키 없음
try:
    data = {'name': '철수'}
    print(data['age'])
except KeyError:
    print("키가 없습니다")

# IndexError: 인덱스 범위 초과
try:
    arr = [1, 2, 3]
    print(arr[10])
except IndexError:
    print("인덱스 초과")

# FileNotFoundError: 파일 없음
try:
    open('없는파일.txt', 'r')
except FileNotFoundError:
    print("파일이 없습니다")

6. 실전 예제

안전한 파일 처리

def safe_read_json(filename):
    """JSON 파일 안전하게 읽기"""
    try:
        with open(filename, 'r', encoding='utf-8') as f:
            return json.load(f)
    except FileNotFoundError:
        print(f"{filename} 파일이 없습니다")
        return {}
    except json.JSONDecodeError as e:
        print(f"JSON 파싱 에러: {e}")
        return {}
    except Exception as e:
        print(f"예상치 못한 에러: {e}")
        return {}

재시도 로직

import time

def retry_operation(func, max_attempts=3):
    """실패 시 재시도"""
    for attempt in range(max_attempts):
        try:
            return func()
        except Exception as e:
            print(f"시도 {attempt + 1} 실패: {e}")
            if attempt < max_attempts - 1:
                time.sleep(1)
            else:
                raise

# 사용
def unstable_operation():
    import random
    if random.random() < 0.7:
        raise ConnectionError("연결 실패")
    return "성공"

try:
    result = retry_operation(unstable_operation)
    print(result)
except Exception as e:
    print(f"최종 실패: {e}")

예외를 골라 잡는 습관 (안전망)

try/except문제가 터졌을 때 프로그램 전체가 멈추지 않게 받쳐 주는 안전망입니다. Exception만 포괄적으로 잡으면 원인 추적이 어려우므로, 기대할 수 있는 예외 이름을 골라 처리하는 편이 유지보수에 유리합니다.

# ✅ 구체적인 예외 처리
try:
    value = int(user_input)
except ValueError:
    print("숫자를 입력하세요")

# ❌ 너무 광범위한 예외 처리
try:
    value = int(user_input)
except Exception:  # 모든 예외를 잡음 (디버깅 어려움)
    print("에러 발생")

# ✅ 예외 메시지 활용
try:
    file = open('data.txt', 'r')
except FileNotFoundError as e:
    print(f"파일 에러: {e}")

# ✅ 리소스 정리는 finally
try:
    file = open('data.txt', 'r')
    # 작업
finally:
    file.close()  # 항상 실행

정리

핵심 요약

  1. try-except: 예외 처리 기본
  2. finally: 항상 실행 (리소스 정리)
  3. raise: 예외 발생
  4. 커스텀 예외: Exception 상속
  5. 베스트 프랙티스: 구체적 예외, with 문

다음 단계

  • 리스트 컴프리헨션
  • 데코레이터

관련 글

  • Python 환경 설정 | Windows/Mac에서 Python 설치하고 시작하기