본문 바로가기
개발&프로그래밍

[Python] 에러와 예외 처리 기초

by 재아군 2024. 11. 22.

[Python] 에러와 예외 처리 기초

프로그램 실행 중 발생할 수 있는 다양한 예외 상황을 적절히 처리하는 것은 안정적인 프로그램 작성의 핵심이다.

예외 처리의 기본부터 실전 패턴까지 알아보자.

 

try-except 구문

기본 구조

# 기본적인 예외 처리
try:
    number = int(input("숫자를 입력하세요: "))
    result = 10 / number
    print(result)
except ValueError:
    print("올바른 숫자를 입력하세요")
except ZeroDivisionError:
    print("0으로 나눌 수 없습니다")
except:  # 모든 예외 처리 (권장하지 않음)
    print("알 수 없는 에러가 발생했습니다")

try-except-else-finally

try:
    file = open("data.txt", "r")
    content = file.read()
except FileNotFoundError:
    print("파일을 찾을 수 없습니다")
else:
    print("파일 읽기 성공:", content)
finally:
    file.close()  # 예외 발생 여부와 관계없이 실행

자주 발생하는 예외

1. TypeError

# 타입 불일치
def add_numbers(a, b):
    try:
        return a + b
    except TypeError:
        print("숫자만 더할 수 있습니다")
        return None

result = add_numbers("hello", 5)  # TypeError 발생

2. ValueError

# 부적절한 값
def get_positive_number():
    try:
        num = int(input("양수를 입력하세요: "))
        if num <= 0:
            raise ValueError("양수가 아닙니다")
        return num
    except ValueError as e:
        print(f"에러: {e}")
        return None

3. IndexError/KeyError

# 리스트와 딕셔너리 접근 에러
def safe_access():
    my_list = [1, 2, 3]
    my_dict = {"a": 1, "b": 2}

    try:
        value1 = my_list[5]  # IndexError
        value2 = my_dict["c"]  # KeyError
    except IndexError:
        print("리스트 인덱스가 범위를 벗어났습니다")
    except KeyError:
        print("존재하지 않는 키입니다")

예외 처리 패턴

1. EAFP (Easier to Ask for Forgiveness than Permission)

# Python 스타일의 예외 처리
# EAFP 방식
def get_dict_value(dictionary, key):
    try:
        return dictionary[key]
    except KeyError:
        return None

# LBYL 방식 (비Python스러움)
def get_dict_value_lbyl(dictionary, key):
    if key in dictionary:
        return dictionary[key]
    else:
        return None

2. 예외 연쇄

def process_data(data):
    try:
        processed = some_processing(data)
    except Exception as e:
        raise RuntimeError("데이터 처리 실패") from e

3. 컨텍스트 관리자

class FileManager:
    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):
        self.file.close()

# 사용
with FileManager('data.txt') as file:
    content = file.read()

커스텀 예외

기본 커스텀 예외

class CustomError(Exception):
    """기본 커스텀 예외"""
    pass

def validate_age(age):
    if age < 0:
        raise CustomError("나이는 음수일 수 없습니다")

상세 커스텀 예외

class ValidationError(Exception):
    def __init__(self, message, value):
        self.message = message
        self.value = value
        super().__init__(self.message)

class DatabaseError(Exception):
    def __init__(self, message, query):
        self.message = message
        self.query = query
        super().__init__(self.message)

# 사용 예시
def process_user_input(value):
    try:
        if not isinstance(value, (int, float)):
            raise ValidationError("숫자만 입력 가능합니다", value)
        # 처리 로직
    except ValidationError as e:
        print(f"검증 오류: {e.message}, 입력값: {e.value}")

실전 예외 처리 패턴

1. 재시도 패턴

import time

def retry_operation(func, max_attempts=3, delay=1):
    """작업 실패 시 재시도하는 데코레이터"""
    def wrapper(*args, **kwargs):
        attempts = 0
        while attempts < max_attempts:
            try:
                return func(*args, **kwargs)
            except Exception as e:
                attempts += 1
                if attempts == max_attempts:
                    raise e
                time.sleep(delay)
    return wrapper

@retry_operation
def unstable_network_call():
    # 네트워크 요청 시뮬레이션
    import random
    if random.random() < 0.7:
        raise ConnectionError("네트워크 오류")
    return "성공"

2. 로깅과 예외 처리

import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

def divide_numbers(a, b):
    try:
        result = a / b
        logger.info(f"나눗셈 성공: {a} / {b} = {result}")
        return result
    except ZeroDivisionError:
        logger.error(f"0으로 나누기 시도: {a} / {b}")
        raise
    except TypeError as e:
        logger.error(f"타입 에러: {e}")
        raise

예외 처리 팁

  1. 구체적인 예외 처리
    # 나쁜 예
    try:
        # 코드
        pass
    except Exception:
        pass
    
    # 좋은 예
    try:
        # 코드
        pass
    except ValueError:
        # ValueError 처리
        pass
    except TypeError:
        # TypeError 처리
        pass
  2. 구체적인 예외 처리
try:
    raise ValueError("잘못된 값")
except ValueError as e:
    print(f"에러 메시지: {str(e)}")
    print(f"에러 타입: {type(e).__name__}")

 

댓글