3 minute read

오류(Error)는 프로그램 실행 중에 발생하는 예기치 않은 상황을 말합니다. 이러한 오류는 다양한 이유로 발생할 수 있습니다. 예를 들어, 잘못된 사용자 입력, 파일을 찾을 수 없음, 네트워크 연결 실패 등이 있습니다. 예외처리(Exception Handling)는 이러한 오류 상황을 처리하여 프로그램이 중단되지 않고 계속 실행되도록 도와줍니다.

1. 오류와 예외의 개념

1.1 오류가 발생하는 이유

프로그램 실행 중 다양한 이유로 오류가 발생할 수 있습니다.

1. 문법 오류 (Syntax Errors): 파이썬의 문법 규칙을 위반했을 때 발생합니다.

# 잘못된 문법 예시
if x = 5:  # '='는 할당 연산자, 비교는 '=='를 사용해야 함
    print("x는 5입니다.")

2. 논리 오류 (Logical Errors): 문법적으로는 올바르지만, 의도한 대로 동작하지 않는 오류입니다.

# 논리 오류 예시
def calculate_average(numbers):
    return sum(numbers) / len(numbers) + 1  # '+1'이 불필요하게 추가됨

3. 런타임 오류 (Runtime Errors): 프로그램 실행 중에 발생하는 오류입니다.

# 런타임 오류 예시
numbers = [1, 2, 3]
print(numbers[3])  # IndexError: 리스트의 인덱스가 범위를 벗어남

1.2 예외처리의 중요성

예외처리는 다음과 같은 이유로 중요합니다:

1. 프로그램의 비정상적인 종료 방지:

try:
    result = 10 / 0
except ZeroDivisionError:
    print("0으로 나눌 수 없습니다.")
    result = None
print("프로그램이 계속 실행됩니다.")

2. 오류 상황에 대한 적절한 대처:

try:
    file = open("config.txt", "r")
except FileNotFoundError:
    print("설정 파일이 없습니다. 기본 설정을 사용합니다.")
    # 기본 설정을 적용하는 코드

3. 디버깅 용이성 증가:

import logging

try:
    # 복잡한 연산 수행
    result = complex_calculation()
except Exception as e:
    logging.error(f"연산 중 오류 발생: {e}")
    # 오류 로그를 통해 문제 원인 파악 가능

2. 기본적인 예외처리 구조

파이썬에서는 try, except, else, finally 블록을 사용하여 예외를 처리할 수 있습니다.

2.1 try-except 구문

가장 기본적인 예외처리 방법입니다.

try:
    age = int(input("나이를 입력하세요: "))
    if age < 0:
        raise ValueError("나이는 음수일 수 없습니다.")
except ValueError as e:
    print(f"잘못된 입력입니다: {e}")

이 예제에서는 사용자 입력을 받아 정수로 변환하고, 음수인 경우 ValueError를 발생시킵니다. except 블록에서는 이 예외를 잡아 적절한 메시지를 출력합니다.

2.2 여러 예외 처리하기

다양한 예외를 개별적으로 처리할 수 있습니다.

try:
    number = int(input("숫자를 입력하세요: "))
    result = 100 / number
    print(f"결과: {result}")
except ValueError:
    print("올바른 숫자를 입력하세요.")
except ZeroDivisionError:
    print("0으로 나눌 수 없습니다.")
except Exception as e:
    print(f"예상치 못한 오류 발생: {e}")

이 예제는 입력값이 숫자가 아닐 경우(ValueError), 0으로 나누려 할 경우(ZeroDivisionError), 그리고 그 외의 모든 예외(Exception)를 각각 다르게 처리합니다.

2.3 else와 finally 절

elsefinally 절을 사용하여 예외 처리를 더 정교하게 할 수 있습니다.

try:
    file = open("example.txt", "r")
    content = file.read()
except FileNotFoundError:
    print("파일을 찾을 수 없습니다.")
else:
    print(f"파일 내용: {content}")
    file.close()
finally:
    print("파일 처리가 완료되었습니다.")

이 예제에서 else 블록은 예외가 발생하지 않았을 때 실행되며, finally 블록은 예외 발생 여부와 관계없이 항상 실행됩니다.

3. 고급 예외처리 기법

3.1 예외 정보 얻기

예외 객체를 통해 상세한 정보를 얻을 수 있습니다.

try:
    x = 10
    y = x / 0
except Exception as e:
    print(f"예외 타입: {type(e).__name__}")
    print(f"예외 메시지: {str(e)}")
    import traceback
    print(f"스택 트레이스:\n{traceback.format_exc()}")

예외의 타입, 메시지, 그리고 전체 스택 트레이스를 출력합니다.

3.2 예외 다시 발생시키기 (re-raising)

예외를 처리한 후 다시 발생시켜 상위 레벨에서 처리할 수 있게 합니다.

def process_data(data):
    try:
        return int(data)
    except ValueError:
        print("데이터 처리 중 오류 발생")
        raise  # 예외를 다시 발생시킴

try:
    result = process_data("abc")
except ValueError:
    print("최종 예외 처리")

이 예제에서 process_data 함수는 예외를 일부 처리한 후 다시 발생시켜, 호출자에게 예외 처리의 기회를 제공합니다.

3.3 오류 회피

때로는 특정 예외를 무시하고 계속 실행하는 것이 필요할 수 있습니다.

def read_file(filename):
    try:
        with open(filename, "r") as file:
            return file.read()
    except FileNotFoundError:
        return None  # 파일이 없으면 None 반환

content = read_file("config.txt")
if content is None:
    print("설정 파일이 없습니다. 기본 설정을 사용합니다.")
else:
    print("설정 파일을 읽었습니다.")

파일이 없을 경우 예외를 발생시키지 않고 None을 반환하여 오류를 회피합니다.

4. 사용자 정의 예외

4.1 예외 만들기

사용자 정의 예외를 만들어 프로그램의 특정 상황을 더 명확하게 표현할 수 있습니다.

class InsufficientFundsError(Exception):
    def __init__(self, balance, amount):
        self.balance = balance
        self.amount = amount
    
    def __str__(self):
        return f"잔액 부족: 현재 잔액 {self.balance}, 요청 금액 {self.amount}"

def withdraw(balance, amount):
    if amount > balance:
        raise InsufficientFundsError(balance, amount)
    return balance - amount

try:
    new_balance = withdraw(100, 150)
except InsufficientFundsError as e:
    print(e)

은행 계좌에서 출금할 때 잔액이 부족한 경우를 위한 사용자 정의 예외를 생성하는 예제입니다.

4.2 사용자 정의 예외 활용

사용자 정의 예외를 활용하여 프로그램의 다양한 상황을 더 명확하게 처리할 수 있습니다.

class NetworkError(Exception):
    pass

class DatabaseError(Exception):
    pass

def fetch_data():
    # 네트워크 오류 시뮬레이션
    raise NetworkError("서버에 연결할 수 없습니다.")

def process_data():
    try:
        data = fetch_data()
    except NetworkError as e:
        print(f"네트워크 오류: {e}")
        # 네트워크 재연결 시도 등의 처리
    except DatabaseError as e:
        print(f"데이터베이스 오류: {e}")
        # 데이터베이스 복구 시도 등의 처리

process_data()

네트워크 오류와 데이터베이스 오류를 구분하여 처리하는 방법을 보여주는 예제입니다.

Leave a comment