BioPlayground

🧬
목록으로

Python 예외 처리 — try, except, raise

Python에서 에러를 잡고, 발생시키고, 정리하는 예외 처리의 모든 것을 실전 예시로 배웁니다.

중급
|
10
|
검증 완료 (2026-07)
예외 처리try-exceptraisefinally에러 핸들링
진행률0/9 (0%)

Python 예외 처리 — try, except, raise

이 토픽을 마치면

Python에서 에러가 발생하는 원리를 이해하고, try/except로 에러를 잡고, raise로 직접 에러를 발생시키며, finally로 정리 작업을 수행하는 패턴을 익힙니다.


에러는 프로그램을 죽인다

Python에서 에러가 발생하면, 그 즉시 프로그램이 멈춥니다.

python
numbers = [1, 2, 3]
print(numbers[10]) # IndexError: list index out of range
print("This line never runs")

에러가 나는 줄 이후의 모든 코드가 실행되지 않습니다. 웹 서버라면 서버 자체가 죽습니다. 데이터 분석 스크립트라면 1000건 중 999건을 처리하고 1건 때문에 전체가 날아갑니다.

예외 처리는 "에러가 나도 죽지 않고 대응하는 방법"입니다.


try/except — 에러를 잡기

python
try:
result = 10 / 0
except ZeroDivisionError:
print("0으로 나눌 수 없습니다")
print("Program continues") # 이 줄이 실행됨!

try 블록 안에서 에러가 나면, except 블록으로 점프합니다. 프로그램은 멈추지 않고 계속 실행됩니다.

에러 메시지 가져오기

python
try:
value = int("hello")
except ValueError as e:
print(f"Error: {e}") # Error: invalid literal for int() with base 10: 'hello'

as e로 에러 객체를 받으면, 무엇이 잘못됐는지 확인할 수 있습니다.

여러 에러 각각 처리

python
def safe_divide(a, b):
try:
result = a / b
return round(result, 2)
except ZeroDivisionError:
print("Denominator cannot be zero")
return None
except TypeError:
print("Both arguments must be numbers")
return None
safe_divide(10, 0) # Denominator cannot be zero
safe_divide("10", 2) # Both arguments must be numbers
safe_divide(10, 3) # 3.33

에러 종류별로 다른 대응을 할 수 있습니다. except Exception으로 모든 에러를 한 번에 잡을 수도 있지만, 권장하지 않습니다 — 어떤 에러인지 모르면 디버깅이 어렵습니다.


자주 만나는 에러 종류

에러원인예시
ValueError값이 올바르지 않음int("abc")
TypeError타입이 맞지 않음"3" + 5
IndexError인덱스 범위 초과[1,2,3][10]
KeyError딕셔너리에 키 없음{"a": 1}["b"]
FileNotFoundError파일이 없음open("none.txt")
ZeroDivisionError0으로 나눔10 / 0
AttributeError없는 속성/메서드 접근None.upper()

이 7가지가 일상적으로 가장 자주 만나는 에러입니다.


else와 finally

python
try:
f = open("data.txt", "r")
content = f.read()
except FileNotFoundError:
print("File not found")
else:
# try가 성공했을 때만 실행
print(f"Read {len(content)} characters")
finally:
# 성공이든 실패든 무조건 실행
print("Cleanup done")
블록실행 시점
try항상 시도
except에러 발생 시
else에러 없이 성공 시
finally무조건 (성공/실패 모두)

finally는 파일 닫기, 네트워크 연결 종료, 임시 파일 삭제 같은 정리 작업에 씁니다. 에러가 나든 안 나든 반드시 실행되어야 하는 코드입니다.


raise — 에러를 직접 발생시키기

python
def withdraw(balance, amount):
if amount <= 0:
raise ValueError("Withdrawal amount must be positive")
if amount > balance:
raise ValueError(f"Insufficient funds: balance={balance}, requested={amount}")
return balance - amount
try:
new_balance = withdraw(1000, 5000)
except ValueError as e:
print(f"Transaction failed: {e}")
# Transaction failed: Insufficient funds: balance=1000, requested=5000

raise는 "이 상황은 정상이 아니다"를 선언하는 것입니다. 호출하는 쪽에서 try/except로 처리하도록 책임을 넘깁니다.

커스텀 에러 클래스

python
class InsufficientFundsError(Exception):
def __init__(self, balance, amount):
self.balance = balance
self.amount = amount
super().__init__(
f"Cannot withdraw {amount} from balance {balance}"
)
def withdraw(balance, amount):
if amount > balance:
raise InsufficientFundsError(balance, amount)
return balance - amount
try:
withdraw(1000, 5000)
except InsufficientFundsError as e:
print(e) # Cannot withdraw 5000 from balance 1000
print(e.balance) # 1000
print(e.amount) # 5000

Exception을 상속해서 자신만의 에러를 만들 수 있습니다. 에러에 추가 정보(잔액, 요청 금액)를 담을 수 있어서, 에러를 잡는 쪽에서 더 정교하게 대응할 수 있습니다.


실전 패턴 — 파일 처리

python
def read_config(filepath):
try:
with open(filepath, "r", encoding="utf-8") as f:
lines = f.readlines()
except FileNotFoundError:
print(f"Config file not found: {filepath}")
return {}
except PermissionError:
print(f"Permission denied: {filepath}")
return {}
config = {}
for i, line in enumerate(lines, 1):
line = line.strip()
if not line or line.startswith("#"):
continue
if "=" not in line:
print(f"Warning: invalid format at line {i}: {line}")
continue
key, value = line.split("=", 1)
config[key.strip()] = value.strip()
return config
# config.txt:
# host = localhost
# port = 3000
# # this is a comment
settings = read_config("config.txt")
print(settings) # {'host': 'localhost', 'port': '3000'}

이 패턴은 실무에서 매우 자주 사용됩니다:

  1. 파일 열기 실패를 except로 처리
  2. 파싱 중 잘못된 행은 경고만 출력하고 건너뜀
  3. 빈 줄과 주석(#)은 무시

프로그램이 "설정 파일 하나 못 열었다고 죽는" 대신, 기본값으로 동작하거나 경고를 보여줍니다.


안티패턴 — 하면 안 되는 것들

모든 에러를 무시

python
# Bad — 에러를 삼켜버림
try:
risky_operation()
except Exception:
pass # What happened? Nobody knows
# Good — 최소한 로그는 남김
try:
risky_operation()
except Exception as e:
print(f"Warning: {e}") # or logging.warning(...)

except: pass는 "모든 에러를 무시"합니다. 프로그램은 죽지 않지만, 무엇이 잘못됐는지 전혀 알 수 없습니다. 나중에 디버깅할 때 원인을 찾기 매우 어렵습니다.

너무 넓은 except

python
# Bad — 타이핑 실수(NameError)도 잡아버림
try:
valeu = int(input("Enter number: ")) # typo: valeu
except Exception:
print("Invalid input") # NameError인데 "Invalid input"?
# Good — 예상하는 에러만 잡기
try:
value = int(input("Enter number: "))
except ValueError:
print("Invalid input — please enter a number")

핵심 정리

구문역할사용 시점
try에러가 날 수 있는 코드 감싸기외부 입력, 파일, 네트워크 등
except특정 에러 잡아서 대응에러별 다른 처리 필요 시
else성공 시에만 실행에러 없을 때 추가 작업
finally무조건 실행정리 작업 (파일 닫기, 연결 종료)
raise에러 직접 발생잘못된 입력, 비즈니스 규칙 위반

예외 처리의 핵심 원칙: 예상하는 에러만, 가능한 좁게 잡고, 무시하지 말 것. "일단 try/except로 감싸면 안전하겠지"는 오해입니다. 잘못된 예외 처리는 에러를 숨겨서, 에러가 없을 때보다 디버깅을 더 어렵게 만듭니다.