에러는 태도만 나쁠 뿐, 결국 하나의 값이다
파이썬에서 뭔가 잘못되면 — 0으로 나누거나, 없는 파일을 열거나, 이상한 숫자를 파싱하거나 — 런타임이 예외 객체를 만들어서 그걸 잡아주는 곳이 나올 때까지 호출 스택을 거슬러 올라갑니다. 끝까지 아무도 잡지 않으면 프로그램은 죽고 트레이스백이 출력되죠.
예외 자체는 나쁜 게 아닙니다. "이 작업은 더 진행할 수 없어요, 이유는 이겁니다"라고 파이썬이 알려주는 방식일 뿐이에요. 개발자가 할 일은 각 상황마다 "이건 내가 복구할 수 있는 에러다", "이건 그냥 위로 올려보내야 한다"를 구분해서 판단하는 겁니다.
기본 구조
try:는 문제가 생길 수 있는 코드 블록을 여는 키워드입니다.except ValueError:는try안에서 해당 예외가 발생했을 때만 잡아냅니다.- 예외가 발생하지 않으면
except블록은 아예 실행되지 않고 건너뜁니다.
위 코드에 42를 넣으면 정상 동작하고, hello를 넣으면 예외 처리 코드가 실행됩니다.
특정 예외만 골라서 잡기
파이썬에는 예외 타입이 계층 구조로 정리되어 있습니다. 자주 마주치게 될 대표적인 예외들은 다음과 같습니다.
ValueError— 값이 잘못된 경우 (int("abc")나 허용 범위를 벗어난 인자 등)TypeError— _타입_이 잘못된 경우 ("hi" + 3처럼)KeyError— 딕셔너리에 해당 키가 없을 때IndexError— 시퀀스의 인덱스가 범위를 벗어났을 때FileNotFoundError— 파일이 존재하지 않을 때ZeroDivisionError— 0으로 나누려고 했을 때AttributeError— 객체에 해당 속성이 없을 때
처리할 수 있는 예외만 콕 집어서 잡아주는 게 좋습니다.
여러 예외를 한꺼번에 잡고 싶다면, 튜플로 묶어서 하나의 except 절에 넘기면 됩니다.
as e 부분을 눈여겨보세요. 예외 객체를 e라는 이름에 연결해 주기 때문에, 메시지나 속성을 꺼내 볼 수 있습니다.
모든 예외를 뭉뚱그려 잡지 말 것
아무 타입도 명시하지 않은 except:는 말 그대로 모든 걸 다 잡아냅니다. KeyboardInterrupt(Ctrl-C)나 시스템 종료 신호까지 포함해서요. 쓰지 마세요.
except Exception:은 그나마 낫긴 하지만 여전히 위험합니다. 예상하지 못한 버그까지 삼켜버려서 문제의 진짜 원인을 가려버리거든요:
# Don't do this without a really good reason.
try:
do_something()
except Exception:
pass
복구 방법을 아는 특정 예외만 잡아내는 것이 거의 항상 정답입니다. 예상치 못한 예외가 프로그램 최상단까지 올라오면 트레이스백이 무엇이 잘못됐는지 정확히 알려주는데, 이건 버그가 아니라 오히려 장점입니다.
else와 finally
try 문에는 선택적으로 사용할 수 있는 절이 두 개 더 있습니다.
else는try블록이 예외 없이 끝났을 때 실행됩니다.finally는 예외가 발생하든 말든 무조건 실행됩니다.
else는 "성공했을 때만" 실행할 코드를 두기에 가장 깔끔한 자리입니다. try 블록 안에는 실제로 실패할 수 있는 부분만 두는 게 좋고, 나머지는 빼두는 게 원칙이죠. finally는 try가 실패하더라도 반드시 실행되어야 하는 정리 작업용입니다. 리소스 닫기, 락 해제, 상태 복구 같은 것들이죠.
예외 직접 발생시키기
내가 작성한 코드에서 에러를 알리고 싶다면 raise를 사용합니다:
상황에 맞는 예외 타입을 고르세요. 자체 클래스를 만들기 전에 ValueError, TypeError, FileNotFoundError 같은 내장 예외부터 써보는 게 좋습니다.
사용자 정의 예외 만들기
내장 예외로는 의미가 제대로 전달되지 않을 때는 직접 예외 클래스를 정의합니다.
Exception(혹은 더 구체적인 내장 예외)을 상속받고, 클래스에 docstring만 달아주면 보통 끝입니다. 커스텀 예외를 만들어두면, 호출하는 쪽에서 딱 해당 도메인에 의미 있는 에러만 골라 잡을 수 있다는 게 장점이죠.
raise ... from ...: 예외 체이닝
하나의 예외가 또 다른 예외를 일으킬 때는, 그 연결 고리를 살려두는 게 좋습니다:
from e를 붙이면 원래 발생한 예외가 그대로 연결됩니다. 트레이스백을 출력할 때 파이썬이 두 예외를 모두 보여주죠 — 겉으로 드러난 ConfigError와 그 원인이 된 FileNotFoundError를 함께요. 디버깅할 때 이런 흔적이 얼마나 고마운지 모릅니다.
컨텍스트 매니저: 더 깔끔하게 정리하는 방법
finally도 나쁘진 않지만, 파일 같은 리소스를 다룰 때는 컨텍스트 매니저(with 문이 사용하는 그것)가 거의 항상 더 나은 선택입니다:
# finally version
f = open("data.txt")
try:
data = f.read()
finally:
f.close()
# with version
with open("data.txt") as f:
data = f.read()
둘 다 안전합니다. with 문이 더 간결한 데다 정리 작업도 알아서 처리해주죠. finally는 표준 라이브러리가 컨텍스트 매니저로 감싸주지 않는 경우에만 쓰면 됩니다.
예외를 잡지 말아야 할 때
예외를 잡는다는 건 하나의 결정 입니다. "이 상황은 내가 처리할 수 있어"라고 선언하는 셈이죠. 처리할 방법이 없다면 그냥 예외가 위로 전파되도록 내버려 두세요. 아래 같은 코드는 거의 대부분 잘못된 패턴입니다:
try:
do_work()
except Exception:
pass # silently ignore everything
에러를 조용히 삼켜버리면 버그가 눈에 보이지 않게 됩니다. 어설프게 잘못된 상태로 계속 굴러가는 것보다, 차라리 요란하게 터뜨리는 편이 낫습니다.
정리
try/except는 복구할 수 있는 에러를 처리할 때 사용합니다.Exception을 뭉뚱그려 잡지 말고, 구체적인 예외를 잡으세요.- 직접 작성한 코드에서 에러를 알릴 때는
raise를 씁니다. - 대부분의
finally정리 작업은with블록으로 대체할 수 있습니다. - 애매하면 예외를 그대로 전파시키세요.
다음 글에서는 파이썬에서 가장 자주 마주치는 에러들 — KeyError, ValueError, ModuleNotFoundError 등 — 을 하나씩 살펴보고, 이런 에러를 빠르게 잡아내는 디버깅 습관까지 함께 다뤄보겠습니다.
자주 묻는 질문
파이썬에서 에러는 어떻게 처리하나요?
에러가 날 만한 코드는 try 블록에 넣고, 잡고 싶은 예외를 except 블록에서 구체적으로 지정하면 됩니다. 예: try: risky() except ValueError: .... 추가로 else는 예외가 발생하지 않았을 때만 실행되고, finally는 예외 여부와 상관없이 항상 실행되므로 리소스 정리에 쓰기 좋습니다.
혹시 모르니까 그냥 Exception을 다 잡아버리면 안 되나요?
추천하지 않습니다. 아무 조건 없이 except:나 except Exception:으로 싹 잡아버리면 예상치 못한 버그까지 조용히 묻혀버립니다. 내가 복구할 수 있는 예외만 구체적으로 잡고, 나머지는 그대로 전파시켜서 진짜 원인이 드러나게 두는 게 좋습니다.
raise와 raise from은 어떻게 다른가요?
raise NewError(...)는 새로운 예외를 발생시킵니다. 반면 raise NewError(...) from original은 원래 예외를 '원인(cause)'으로 연결해서 던지기 때문에, 트레이스백에도 두 예외가 함께 표시됩니다. 저수준 에러를 잡아서 좀 더 의미 있는 상위 예외로 바꿔 던질 때 from을 쓰면 원인 추적이 훨씬 쉬워집니다.