본문 바로가기
Python

22. 에러와 예외처리 - 프로그램이 죽지 않게

by 샤나엘 2026. 2. 22.
반응형

에러와 예외처리

22. 에러와 예외처리 - 프로그램이 죽지 않게

프로그램은 실행 중에 에러를 만날 수밖에 없다. 사용자가 숫자 대신 문자를 입력하거나, 없는 파일을 열려고 하거나. 중요한 건 에러가 나지 않게 하는 것이 아니라, 에러가 나도 프로그램이 죽지 않게 하는 것이다.


에러의 종류

파이썬에서 에러는 크게 문법 에러예외(Exception)로 나뉜다.

문법 에러 (SyntaxError)

코드 자체가 잘못되어 실행조차 안 되는 에러. 오타, 괄호 미닫힘, 콜론 빠뜨림 등.

# SyntaxError 예시
print("Hello"       # 괄호 안 닫음
if True             # 콜론 빠뜨림
    print("hi")
def func(            # 괄호 안 닫음

⚠️ SyntaxError는 try/except로 잡을 수 없다. 코드를 수정해야 한다.

예외 (Exception)

코드 문법은 맞지만, 실행 중에 발생하는 에러.

# TypeError — 타입이 맞지 않음
print("나이: " + 25)          # str + int 불가

# ValueError — 값이 적절하지 않음
int("hello")                  # 문자열을 정수로 변환 불가

# IndexError — 인덱스 범위 초과
lst = [1, 2, 3]
print(lst[10])                # 존재하지 않는 인덱스

# KeyError — 딕셔너리에 없는 키
d = {"name": "철수"}
print(d["age"])               # 존재하지 않는 키

# ZeroDivisionError — 0으로 나눔
print(10 / 0)                 # 0으로 나눌 수 없음

# FileNotFoundError — 파일이 없음
open("없는파일.txt")           # 존재하지 않는 파일

# NameError — 정의되지 않은 이름
print(undefined_variable)     # 정의 안 된 변수

 

에러 종류 정리


try / except — 예외 잡기

try 블록 안의 코드에서 에러가 발생하면, 프로그램이 죽지 않고 except 블록으로 이동한다.

try:
    result = 10 / 0
except:
    print("에러가 발생했습니다!")

print("프로그램 계속 실행...")
# 에러가 발생했습니다!
# 프로그램 계속 실행...

특정 에러만 잡기

try:
    num = int(input("숫자 입력: "))
    result = 100 / num
    print(f"결과: {result}")
except ValueError:
    print("숫자를 입력해주세요!")
except ZeroDivisionError:
    print("0으로 나눌 수 없습니다!")

에러 메시지 가져오기

try:
    result = 10 / 0
except ZeroDivisionError as e:
    print(f"에러 발생: {e}")
# 에러 발생: division by zero

여러 에러 한번에 잡기

try:
    # 여러 종류의 에러가 날 수 있는 코드
    data = [1, 2, 3]
    print(data[int("abc")])
except (ValueError, IndexError) as e:
    print(f"에러: {e}")

try / except / else / finally

try / except 실행 흐름

try:
    num = int(input("숫자 입력: "))
    result = 100 / num
except ValueError:
    print("숫자를 입력해주세요!")
except ZeroDivisionError:
    print("0으로 나눌 수 없습니다!")
else:
    # 에러가 없을 때만 실행
    print(f"결과: {result}")
finally:
    # 에러 여부와 상관없이 항상 실행
    print("프로그램 종료")

else — 에러가 없을 때만

try:
    f = open("data.txt", "r")
except FileNotFoundError:
    print("파일이 없습니다!")
else:
    # 파일 열기 성공 시에만 실행
    content = f.read()
    print(content)
    f.close()

finally — 무조건 실행

finally는 에러가 나든 안 나든 무조건 실행된다. 파일 닫기, 연결 해제 등 정리 작업에 사용한다.

f = None
try:
    f = open("data.txt", "r")
    content = f.read()
    print(content)
except FileNotFoundError:
    print("파일이 없습니다!")
finally:
    if f:
        f.close()
        print("파일을 닫았습니다.")

raise — 직접 에러 발생시키기

raise의도적으로 에러를 발생시킬 수 있다.

def set_age(age):
    if age < 0:
        raise ValueError("나이는 음수일 수 없습니다!")
    if age > 150:
        raise ValueError("나이가 너무 큽니다!")
    print(f"나이가 {age}으로 설정되었습니다.")

try:
    set_age(25)     # 나이가 25으로 설정되었습니다.
    set_age(-5)     # ValueError 발생!
except ValueError as e:
    print(f"에러: {e}")
# 에러: 나이는 음수일 수 없습니다!
def divide(a, b):
    if not isinstance(a, (int, float)) or not isinstance(b, (int, float)):
        raise TypeError("숫자만 입력 가능합니다!")
    if b == 0:
        raise ZeroDivisionError("0으로 나눌 수 없습니다!")
    return a / b

try:
    print(divide(10, 3))         # 3.333...
    print(divide("10", 3))       # TypeError!
except (TypeError, ZeroDivisionError) as e:
    print(f"에러: {e}")

커스텀 예외 만들기

Exception을 상속받아 나만의 예외를 만들 수 있다.

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

class InvalidAmountError(Exception):
    """잘못된 금액 예외"""
    pass

def withdraw(balance, amount):
    if amount <= 0:
        raise InvalidAmountError("출금액은 0보다 커야 합니다!")
    if amount > balance:
        raise InsufficientBalanceError(balance, amount)
    return balance - amount

# 사용
try:
    balance = 10000
    balance = withdraw(balance, 3000)
    print(f"출금 성공! 잔액: {balance}원")    # 7000원
    balance = withdraw(balance, 50000)
except InsufficientBalanceError as e:
    print(f"출금 실패: {e}")
    print(f"  현재 잔액: {e.balance}원, 요청 금액: {e.amount}원")
except InvalidAmountError as e:
    print(f"입력 오류: {e}")
# 출금 실패: 잔액 부족! 잔액: 7000원, 출금: 50000원
#   현재 잔액: 7000원, 요청 금액: 50000원

실전 패턴

패턴 1: 사용자 입력 검증

def get_integer(prompt):
    """정수를 입력받을 때까지 반복"""
    while True:
        try:
            return int(input(prompt))
        except ValueError:
            print("정수를 입력해주세요!")

# age = get_integer("나이 입력: ")
# print(f"입력한 나이: {age}")

패턴 2: 파일 읽기 안전하게

def read_file_safe(filepath):
    """파일을 안전하게 읽기"""
    try:
        with open(filepath, "r", encoding="utf-8") as f:
            return f.read()
    except FileNotFoundError:
        print(f"파일을 찾을 수 없습니다: {filepath}")
        return None
    except PermissionError:
        print(f"파일 읽기 권한이 없습니다: {filepath}")
        return None
    except UnicodeDecodeError:
        print(f"파일 인코딩 오류: {filepath}")
        return None

content = read_file_safe("data.txt")
if content:
    print(content)

패턴 3: 딕셔너리 접근

user = {"name": "김철수", "age": 25}

# KeyError 방지 방법 1: try/except
try:
    email = user["email"]
except KeyError:
    email = "이메일 없음"

# KeyError 방지 방법 2: get() 메서드 (더 간편)
email = user.get("email", "이메일 없음")
print(email)    # 이메일 없음

패턴 4: 리스트 안전 접근

def safe_get(lst, index, default=None):
    """리스트에서 안전하게 값 가져오기"""
    try:
        return lst[index]
    except IndexError:
        return default

numbers = [10, 20, 30]
print(safe_get(numbers, 1))       # 20
print(safe_get(numbers, 10))      # None
print(safe_get(numbers, 10, 0))   # 0

패턴 5: 여러 작업 순차 처리

def process_data(data_list):
    """데이터를 하나씩 처리, 에러 나면 건너뛰기"""
    results = []
    errors = []

    for i, item in enumerate(data_list):
        try:
            result = int(item) ** 2
            results.append(result)
        except (ValueError, TypeError) as e:
            errors.append(f"[{i}] {item}: {e}")

    return results, errors

data = ["3", "5", "abc", "7", None, "10"]
results, errors = process_data(data)
print(f"성공: {results}")     # [9, 25, 49, 100]
print(f"실패: {errors}")
# ['[2] abc: invalid literal...', '[4] None: int() argument...']

직접 해보기

문제 1. 사용자에게 두 수를 입력받아 나눗셈을 수행하는 프로그램을 만들어보자. ValueError와 ZeroDivisionError를 처리할 것.

문제 2. 리스트에서 인덱스로 값을 가져오는 safe_index(lst, idx) 함수를 만들어보자. IndexError 시 "인덱스 초과"를 반환.

문제 3. 점수(0~100)를 입력받아 학점을 반환하는 함수를 만들어보자. 범위 밖이면 ValueError를 raise할 것.

문제 4. NegativeNumberError라는 커스텀 예외를 만들고, 음수가 입력되면 이 예외를 발생시키는 sqrt_safe(n) 함수를 만들어보자.

문제 5. 문자열 리스트를 정수로 변환하되, 변환 실패한 항목은 0으로 처리하는 convert_to_int(str_list) 함수를 만들어보자.

정답 보기
# 문제 1
def safe_divide():
    try:
        a = float(input("첫 번째 수: "))
        b = float(input("두 번째 수: "))
        result = a / b
    except ValueError:
        print("숫자를 입력해주세요!")
    except ZeroDivisionError:
        print("0으로 나눌 수 없습니다!")
    else:
        print(f"{a} / {b} = {result}")
‍
# safe_divide()
‍
# 문제 2
def safe_index(lst, idx):
    try:
        return lst[idx]
    except IndexError:
        return "인덱스 초과"
‍
data = [10, 20, 30]
print(safe_index(data, 1))     # 20
print(safe_index(data, 10))    # 인덱스 초과
‍
# 문제 3
def get_grade(score):
    if not isinstance(score, (int, float)):
        raise TypeError("숫자만 입력 가능합니다!")
    if score < 0 or score > 100:
        raise ValueError(f"점수는 0~100 사이여야 합니다: {score}")
    if score >= 90: return "A"
    elif score >= 80: return "B"
    elif score >= 70: return "C"
    elif score >= 60: return "D"
    else: return "F"
‍
try:
    print(get_grade(85))     # B
    print(get_grade(150))    # ValueError!
except ValueError as e:
    print(f"에러: {e}")
‍
# 문제 4
class NegativeNumberError(Exception):
    pass
‍
import math
‍
def sqrt_safe(n):
    if n < 0:
        raise NegativeNumberError(f"음수의 제곱근은 구할 수 없습니다: {n}")
    return math.sqrt(n)
‍
try:
    print(sqrt_safe(16))    # 4.0
    print(sqrt_safe(-4))    # NegativeNumberError!
except NegativeNumberError as e:
    print(f"에러: {e}")
‍
# 문제 5
def convert_to_int(str_list):
    result = []
    for item in str_list:
        try:
            result.append(int(item))
        except (ValueError, TypeError):
            result.append(0)
    return result
‍
data = ["10", "abc", "30", None, "50", "xyz"]
print(convert_to_int(data))    # [10, 0, 30, 0, 50, 0]

오늘의 정리

항목 내용
SyntaxError 문법 오류. 코드 자체가 잘못됨. try/except 불가
예외 (Exception) 실행 중 발생하는 에러. TypeError, ValueError 등
try/except 에러 발생 시 except 블록으로 이동. 프로그램 유지
else 에러가 없을 때만 실행되는 블록
finally 에러 여부와 상관없이 항상 실행. 정리 작업용
raise 의도적으로 에러 발생. raise ValueError("메시지")
커스텀 예외 class MyError(Exception): pass
as e except Error as e:로 에러 메시지 가져오기

다음 편 예고: 클래스와 객체지향 (1) - 나만의 타입 만들기

파이썬의 모든 것은 객체다. 이제 직접 클래스를 만들어보자. 객체지향 프로그래밍의 첫 발을 내딛는다.


태그: 파이썬 Python 파이썬독학 예외처리 try except finally 에러 raise 파이썬기초 IT교육

반응형

댓글