코드를 작성하다 보면 예상치 못한 문제에 부딪히기 마련입니다. 이러한 문제들은 때로는 프로그램 전체를 멈추게 만들기도 하는데, 이때 필요한 것이 바로 ‘에러 핸들링’입니다. 이 챕터에서는 파이썬에서 발생하는 다양한 오류를 현명하게 다루는 기본기를 저와 함께 탄탄하게 다져보겠습니다.
핵심 요약: 에러 핸들링은 프로그램의 안정성과 사용자 경험을 결정짓는 핵심 요소입니다.
try-except구문은 예기치 않은 오류로부터 프로그램을 보호하는 파이썬의 기본 메커니즘입니다.- 특정 예외를 명시적으로 처리하여 코드의 안정성과 가독성을 높일 수 있습니다.
finally구문은 예외 발생 여부와 상관없이 반드시 실행되어야 하는 코드를 보장합니다.raise키워드를 활용해 코드의 특정 조건에서 의도적으로 예외를 발생시킬 수 있습니다.
에러 핸들링, 왜 중요할까요?
코드의 안정성을 확보하고 사용자에게 좋은 경험을 제공하기 위해 에러 핸들링은 필수적입니다.
저도 처음 코딩을 시작했을 때, 프로그램이 ‘오류 발생’ 메시지와 함께 갑자기 꺼지는 경험을 자주 했습니다. 그때마다 당황스러웠고, 어디서부터 손대야 할지 막막했지요. 현업에서 작은 자동화 스크립트를 돌리거나, 데이터 분석 파이프라인을 구축할 때도 마찬가지입니다. 예상치 못한 데이터 형식이나 네트워크 문제로 인해 스크립트가 멈춰버리면, 그만큼 작업이 지연되고 시간을 손해 보게 됩니다.
에러 핸들링은 단순히 오류를 피하는 것을 넘어, 프로그램이 문제가 생겼을 때도 우아하게 대처할 수 있도록 만드는 과정입니다. 예를 들어, 사용자가 숫자를 입력해야 하는 곳에 문자를 입력했을 때, 프로그램이 멈추는 대신 “숫자를 입력해 주세요!”라는 친절한 메시지를 보여주고 다시 입력을 요청하는 것이 훨씬 나은 사용자 경험을 제공하겠죠? 현재 2026년 기준으로, AI 기반 애플리케이션 개발에서도 에러 핸들링은 예상치 못한 입력이나 모델 오작동으로부터 시스템을 보호하는 중요한 방어선 역할을 합니다.
파이썬에서 에러와 예외를 구분하기
파이썬은 문법 오류(Error)와 실행 중 발생하는 문제(Exception)를 구분하여 처리합니다.
간단히 말해, 에러(Error)는 코드를 실행하기 전에 발생하는 문제, 예를 들어 문법이 틀렸을 때 나타납니다. 파이썬 인터프리터가 코드를 읽다가 ‘이건 문법에 안 맞아!’라고 알려주는 것이죠. 코드를 저장하고 실행하기 전부터 눈에 띄게 나타나는 경우가 많습니다. 예를 들어, 괄호를 닫지 않거나 `def` 대신 `deh`라고 오타를 내는 경우입니다.
반면 예외(Exception)는 코드는 문법적으로 옳지만, 프로그램이 실행되는 도중에 예상치 못한 상황이 발생했을 때 나타납니다. 예를 들어, 없는 파일을 열려고 하거나, 0으로 나누려 할 때 등이요. 이런 예외들은 미리 예측하고 대비할 수 있으며, 파이썬에서는 이를 처리하기 위한 특별한 구문을 제공합니다.
try-except 구문으로 예외 잡기: 기본 중의 기본
try 블록에서 예외 발생 가능성이 있는 코드를 실행하고, except 블록에서 해당 예외를 처리합니다.
파이썬에서 예외를 처리하는 가장 기본적인 방법은 try-except 구문을 사용하는 것입니다. 마치 ‘일단 시도해 보고(try), 만약 문제가 생기면(except) 이렇게 처리해!’라고 지시하는 것과 같습니다.
예제 1: 0으로 나누기 예외 처리
try 블록 안의 코드는 실행되지만, 만약 예외가 발생하면 즉시 except 블록으로 이동하여 예외를 처리합니다.
def divide_numbers(a, b):
try:
result = a / b
print(f"나눗셈 결과: {result}")
except ZeroDivisionError:
print("오류: 0으로 나눌 수 없습니다!")
divide_numbers(10, 2) # 나눗셈 결과: 5.0
divide_numbers(10, 0) # 오류: 0으로 나눌 수 없습니다!
이 코드가 하는 일: divide_numbers 함수는 두 수를 나누는 연산을 수행합니다. 이때 두 번째 인자가 0이면 ZeroDivisionError가 발생하는데, except ZeroDivisionError 블록이 이를 잡아내어 사용자에게 친절한 메시지를 출력합니다.
저도 취미로 개발하던 초창기에 이런 예외 처리가 부족해서, 간단한 계산기 프로그램을 만들다가 0으로 나누는 순간 프로그램이 멈춰버렸던 경험이 생생합니다. try-except 덕분에 이제는 걱정 없이 복잡한 연산도 시도해 볼 수 있게 되었습니다.
특정 예외만 골라 처리하는 방법
여러 except 블록을 사용하여 발생 가능한 다양한 예외 유형에 맞춰 개별적으로 처리할 수 있습니다.
때로는 한 가지 예외만 발생하는 것이 아니라, 여러 종류의 예외가 발생할 수 있습니다. 예를 들어, 사용자 입력을 받아 숫자로 변환하는 과정에서는 숫자가 아닌 문자를 입력하면 ValueError가, 0으로 나누면 ZeroDivisionError가 발생할 수 있죠. 이때는 여러 개의 except 블록을 사용하여 각 예외에 맞는 처리를 할 수 있습니다.
def process_input(user_input):
try:
num = int(user_input) # 문자열을 숫자로 변환
result = 100 / num
print(f"처리 결과: {result}")
except ValueError:
print("오류: 유효한 숫자를 입력해 주세요!")
except ZeroDivisionError:
print("오류: 0이 아닌 숫자를 입력해 주세요!")
except Exception as e: # 예상치 못한 다른 예외를 처리
print(f"알 수 없는 오류 발생: {e}")
process_input("abc") # 오류: 유효한 숫자를 입력해 주세요!
process_input("0") # 오류: 0이 아닌 숫자를 입력해 주세요!
process_input("20") # 처리 결과: 5.0
이 코드가 하는 일: process_input 함수는 사용자 입력을 숫자로 변환하고 100을 나눕니다. 입력이 숫자가 아니면 ValueError, 0이면 ZeroDivisionError를 각각 처리합니다. 마지막 except Exception as e는 위에 명시되지 않은 다른 모든 예외를 포괄적으로 처리하여 프로그램이 갑자기 종료되는 것을 방지합니다. 특정 오류 패턴에 대한 자세한 내용은 흔한 오류 패턴 분석 챕터에서 더 깊이 다루니 참고하시면 좋습니다.
finally 구문으로 마무리 작업 처리하기
finally 블록은 try 블록에서 예외 발생 여부와 상관없이 항상 실행되어야 하는 코드를 포함합니다.
파일을 열거나 네트워크 연결을 설정하는 등 특정 자원을 사용한 후에는 예외 발생 여부와 관계없이 해당 자원을 반드시 해제해야 하는 경우가 많습니다. 이때 finally 구문을 사용하면 유용합니다.
def read_file_safely(filename):
file = None
try:
file = open(filename, 'r')
content = file.read()
print("파일 내용:")
print(content)
except FileNotFoundError:
print(f"오류: '{filename}' 파일을 찾을 수 없습니다.")
except PermissionError:
print(f"오류: '{filename}' 파일에 접근 권한이 없습니다.")
finally:
if file:
file.close()
print(f"'{filename}' 파일이 닫혔습니다.")
read_file_safely("non_existent.txt") # 파일 없음 오류 발생 후 닫힘 메시지
print("-----------------")
# 'test.txt' 파일을 만들고 실행해 보세요.
# with open("test.txt", "w") as f:
# f.write("Hello, Python!")
read_file_safely("test.txt") # 정상 실행 후 닫힘 메시지
이 코드가 하는 일: read_file_safely 함수는 지정된 파일을 읽으려고 시도합니다. 파일이 없거나(FileNotFoundError) 권한이 없으면(PermissionError) 예외를 처리하지만, 어떤 경우든 finally 블록이 실행되어 열려 있던 파일 자원을 안전하게 닫습니다. 이는 시스템 자원 낭비를 막고 잠재적인 문제를 방지하는 중요한 단계입니다.
예외 발생시키기: raise 키워드
raise 키워드를 사용하면 특정 조건에서 프로그래머가 의도적으로 예외를 발생시킬 수 있습니다.
때로는 코드의 특정 조건이 충족되지 않을 때, 사용자에게 명확히 알려주거나 상위 호출자에게 문제를 전달하기 위해 직접 예외를 발생시켜야 할 필요가 있습니다. 이때 raise 키워드를 사용합니다.
def set_age(age):
if not isinstance(age, int):
raise TypeError("나이는 정수여야 합니다.")
if not (0 < age < 150):
raise ValueError("나이는 0보다 크고 150보다 작아야 합니다.")
print(f"나이가 {age}로 설정되었습니다.")
try:
set_age(30) # 나이가 30로 설정되었습니다.
set_age(-5) # ValueError 발생
set_age("스무살") # TypeError 발생
except (TypeError, ValueError) as e:
print(f"오류 발생: {e}")
이 코드가 하는 일: set_age 함수는 나이를 설정합니다. 만약 나이가 정수가 아니거나 유효 범위(0~150세)를 벗어나면, 각각 TypeError 또는 ValueError를 발생시킵니다. 외부 try-except 블록에서 이 예외들을 잡아 처리하여 프로그램이 의도한 대로 동작하도록 합니다. 이는 데이터 유효성 검사 등에서 매우 유용하게 쓰입니다.
실전 예제: 사용자 입력 처리 시 에러 핸들링
안정적인 사용자 입력을 위해 반복문과 try-except를 결합하여 견고한 코드 로직을 만듭니다.
이전 챕터에서 조건문과 반복문 활용에 대해 배우셨을 텐데요. 이를 에러 핸들링과 결합하면 훨씬 강력한 프로그램을 만들 수 있습니다. 사용자로부터 유효한 숫자를 입력받을 때까지 반복해서 요청하는 프로그램을 만들어 보겠습니다. 이는 마치 간단한 메모장 프로그램에서 ‘몇 번째 줄을 편집할까요?’ 같은 질문에 숫자가 아닌 다른 것을 입력했을 때의 상황과 유사합니다.
def get_valid_number_input():
while True:
try:
user_input = input("숫자를 입력해 주세요: ")
number = int(user_input)
return number
except ValueError:
print("오류: 올바른 숫자가 아닙니다. 다시 입력해 주세요.")
except KeyboardInterrupt: # Ctrl+C 와 같은 강제 종료 시 예외
print("프로그램을 종료합니다.")
break
# 프로그램 실행
print("유효한 숫자 입력기 시작.")
valid_num = get_valid_number_input()
if valid_num is not None:
print(f"입력된 유효한 숫자: {valid_num}")
print("프로그램 종료.")
이 코드가 하는 일: get_valid_number_input 함수는 무한 반복문(while True)을 사용하여 사용자에게 숫자를 입력받습니다. 만약 사용자가 숫자가 아닌 값을 입력하면 ValueError가 발생하고, except ValueError 블록이 실행되어 오류 메시지를 출력하고 다시 입력을 요청합니다. 유효한 숫자가 입력되면 해당 숫자를 반환하며 반복문을 탈출합니다. KeyboardInterrupt 예외까지 처리하여 사용자가 프로그램 강제 종료 시에도 깔끔하게 메시지를 보여줍니다. 제가 데이터 수집 자동화 스크립트를 만들 때, 사용자에게 설정을 입력받는 부분에서 이 패턴을 자주 활용하는데, 덕분에 스크립트가 훨씬 견고해졌습니다.
흔한 실수와 해결법: 무작정 except만 쓰지 마세요!
특정 예외를 명시적으로 처리하는 것이 코드의 신뢰성과 디버깅 효율성을 높입니다.
초보자들이 흔히 하는 실수 중 하나는 모든 예외를 except Exception as e: 와 같이 포괄적으로 처리하는 것입니다. 물론 당장 프로그램이 멈추는 것을 막을 수는 있지만, 이는 양날의 검과 같습니다. 왜냐하면 예상치 못한 버그나 다른 심각한 문제가 발생해도 그저 ‘알 수 없는 오류’로 퉁쳐버리게 되어, 실제 문제가 무엇인지 파악하고 해결하기 매우 어렵게 만들기 때문입니다. 마치 모든 병을 ‘감기약’으로만 치료하려는 것과 같습니다. 이는 효율적인 디버깅 기법을 익히는 데도 방해가 됩니다.
가능한 한 구체적인 예외 유형(예: ValueError, FileNotFoundError)을 명시하여 처리하고, 꼭 필요한 경우에만 Exception을 사용하여 예상치 못한 상황에 대비하는 것이 좋습니다. 이를 통해 코드의 의도를 명확히 하고, 잠재적인 문제를 더 쉽게 발견할 수 있습니다.
FAQ
Q1: try-except를 너무 많이 사용하면 코드 성능에 영향이 있나요?
A1: 예외가 발생했을 때 except 블록으로 점프하는 과정은 일반적인 코드 실행보다 약간의 오버헤드가 발생할 수 있습니다. 하지만 예외가 자주 발생하지 않는 일반적인 상황에서는 미미한 수준이며, 코드의 안정성이라는 이점이 훨씬 큽니다. 성능이 중요한 루프에서 예외를 남용하는 것은 피해야 하지만, 일반적인 에러 핸들링에서는 크게 걱정할 필요가 없습니다.
Q2: 어떤 예외를 처리해야 할지 모르겠어요.
A2: 처음에는 주로 발생할 수 있는 ValueError (타입 변환), TypeError (잘못된 타입 사용), FileNotFoundError (파일 없음), ZeroDivisionError (0으로 나누기) 등 자주 접하는 예외들을 중심으로 처리해 보세요. 코드를 실행하면서 발생하는 오류 메시지를 확인하고, 해당 오류 유형을 except 블록에 추가하는 방식으로 점차 익혀나갈 수 있습니다.
Q3: else 구문은 언제 사용하나요?
A3: try-except 구문에는 else 블록을 추가할 수 있습니다. else 블록은 try 블록의 코드가 아무런 예외 없이 성공적으로 실행되었을 때만 실행됩니다. 예를 들어, 파일을 성공적으로 읽었을 때만 추가적인 작업을 하고 싶을 때 유용하게 사용할 수 있습니다.
마무리하며
지금까지 파이썬의 에러 핸들링 기본기에 대해 알아보았습니다. 코드는 때로 예상치 못한 방향으로 흘러가기도 하지만, try-except-finally 구문과 raise 키워드를 적절히 활용하면 어떤 상황에서도 당황하지 않고 안정적으로 대처할 수 있습니다. 제가 현업에서 수많은 시행착오 끝에 깨달은 것은, 에러 핸들링은 단순히 버그를 수정하는 기술이 아니라, 사용자에게 신뢰를 주는 코드를 만드는 기본 원칙이라는 점입니다. 다음 챕터에서는 이제 이 기본기를 바탕으로 더 복잡한 코드 작성에 필요한 고급 프로그래밍 테크닉을 함께 탐구해 보겠습니다.
참고 자료
- [1] Python 공식 문서 — https://docs.python.org/ko/3/
- [2] Claude Code 공식 문서 — https://docs.claude.com/en/docs/claude-code/overview
