프로그램을 만들다 보면 예상치 못한 문제에 부딪히기 마련입니다. 사용자 입력이 잘못되거나, 파일을 찾을 수 없거나, 숫자를 0으로 나누는 상황처럼 말이죠. 이러한 상황을 우리는 ‘예외(Exception)’라고 부르며, 파이썬은 이 예외를 안전하게 처리할 수 있는 강력한 방법을 제공합니다. 이 챕터에서는 try-except-finally 블록을 활용하여 프로그램의 안정성을 높이는 기초적인 예외 처리 방법에 대해 자세히 알아보겠습니다.
핵심 요약
- 예외(Exception): 프로그램 실행 중 발생하는 예상치 못한 오류를 의미하며, 처리하지 않으면 프로그램이 비정상 종료됩니다.
try-except블록: 예외 발생 가능성이 있는 코드를try에, 예외 발생 시 실행할 코드를except에 작성하여 오류를 감지하고 처리합니다.- 여러 예외 처리:
ValueError,ZeroDivisionError등 다양한 예외를 각except블록에서 구체적으로 처리하여 맞춤형 대응이 가능합니다. finally블록: 예외 발생 여부와 관계없이 항상 실행되어야 하는 정리(cleanup) 코드를 포함하여, 자원 누수 등을 방지합니다.- 활용 예제: 사용자 입력 유효성 검사, 파일 처리 등 프로그램의 안정성이 중요한 부분에 필수적으로 사용됩니다.
프로그램에서 ‘예외’란 무엇인가요?
예외는 프로그램 실행 중에 발생하는 오류를 의미하며, 적절히 처리하지 않으면 프로그램이 갑자기 멈추는 원인이 됩니다. 파이썬에서 코드를 작성하고 실행하다 보면 때때로 예측하지 못한 문제가 발생하여 프로그램이 비정상적으로 종료되는 경험을 하실 수 있습니다. 예를 들어, 사용자가 숫자 대신 글자를 입력하거나, 프로그램이 접근하려는 파일이 존재하지 않는 경우 등이 그렇습니다. 이러한 예상 밖의 상황을 파이썬에서는 ‘예외(Exception)’라고 부릅니다. 저도 취미로 파이썬을 배운 지 10년 차가 되었을 때, 현업에서 단순한 계산기 프로그램을 만들다가 사용자가 0으로 나누는 바람에 프로그램이 멈춰버린 경험이 있습니다. 그때 ‘아, 단순히 코드만 짜는 게 아니라 이런 상황까지 고려해야 하는구나’ 하고 깨달았습니다. 예외는 단순히 오류 메시지를 띄우는 것 이상으로, 프로그램의 흐름을 방해하고 사용자 경험을 해치는 요인이 될 수 있습니다. 따라서 예외가 발생했을 때 프로그램이 멈추지 않고, 정해진 대로 동작하도록 하는 것이 바로 ‘예외 처리’의 핵심입니다.
try-except 블록으로 예외를 잡는 방법은 무엇인가요?
try-except 블록은 특정 코드에서 예외가 발생할 가능성이 있을 때, 해당 예외를 감지하고 처리하는 파이썬의 기본 문법입니다. 예외 처리는 프로그램의 안정성을 확보하는 데 매우 중요합니다. 파이썬에서는 try와 except 키워드를 사용하여 예외를 처리합니다. try 블록 안에 예외가 발생할 가능성이 있는 코드를 작성하고, except 블록에는 try 블록에서 특정 예외가 발생했을 때 실행할 코드를 작성합니다. 마치 ‘이 부분에서 문제가 생길 수도 있으니 조심해서 실행해보고, 만약 문제가 생기면 이렇게 대처해라’ 하고 지시하는 것과 같습니다.
try-except의 기본 구조
다음은 try-except 블록의 기본적인 형태입니다.
try:
# 예외 발생 가능성이 있는 코드
# 예를 들어, 숫자 나누기, 파일 열기 등
result = 10 / 0
print(result)
except ZeroDivisionError:
# ZeroDivisionError 예외가 발생했을 때 실행될 코드
print("오류: 0으로 나눌 수 없습니다.")
이 코드는 0으로 나누는 연산에서 발생하는 ZeroDivisionError 예외를 except 블록에서 처리하여, 프로그램이 강제 종료되는 대신 사용자에게 친절한 메시지를 출력하게 합니다.
여러 예외 처리하기
하나의 try 블록에서 여러 종류의 예외가 발생할 수 있으므로, 각각의 예외에 맞춰 여러 except 블록을 사용할 수 있습니다. 경우에 따라서는 여러 종류의 예외를 처리해야 할 때도 있습니다. 예를 들어, 사용자로부터 숫자를 입력받아 나누는 프로그램이라면, 숫자가 아닌 문자를 입력했을 때 발생하는 ValueError와 0으로 나눌 때 발생하는 ZeroDivisionError를 모두 고려해야 합니다.
try:
num1 = int(input("첫 번째 숫자를 입력하세요: "))
num2 = int(input("두 번째 숫자를 입력하세요: "))
result = num1 / num2
print(f"나눗셈 결과: {result}")
except ValueError:
print("오류: 유효한 숫자를 입력해주세요.")
except ZeroDivisionError:
print("오류: 0으로 나눌 수 없습니다.")
except Exception as e: # 모든 예외를 잡는 일반적인 except 블록
print(f"예상치 못한 오류 발생: {e}")
위 코드에서 int(input(...)) 부분은 문자열을 숫자로 변환하는 과정에서 문제가 생기면 ValueError를 발생시킬 수 있습니다. 또한, 숫자형 데이터를 다루는 부분에서 num1 / num2 연산은 num2가 0일 경우 ZeroDivisionError를 발생시킬 수 있죠. 이처럼 다양한 예외를 각 except 블록에서 개별적으로 처리하여, 어떤 문제가 발생했는지 사용자에게 정확히 알려줄 수 있습니다. 마지막 except Exception as e는 위에 명시되지 않은 모든 종류의 예외를 처리하는 일반적인 블록입니다. 이는 예상치 못한 예외가 발생했을 때 프로그램이 갑자기 멈추는 것을 방지하는 안전망 역할을 합니다.
흔한 실수: 너무 넓은 범위의 예외 잡기
초보자분들이 흔히 저지르는 실수 중 하나는 모든 예외를 except Exception:으로 한 번에 처리하는 것입니다. 저도 처음에는 ‘일단 에러 안 나게 다 잡자!’라는 생각으로 이렇게 코드를 짰던 적이 있습니다. 하지만 이렇게 하면 어떤 예외가 발생했는지 명확하게 알기 어렵고, 디버깅을 어렵게 만듭니다. 특정 예외만 처리하고 싶을 때는 구체적인 예외 타입을 명시하는 것이 좋습니다. 어떤 다양한 예외 유형이 있는지 미리 알아두는 것이 중요합니다.
finally 블록의 역할과 사용 시점은 언제인가요?
finally 블록은 try 블록에서 예외가 발생하든 안 하든, 항상 실행되어야 하는 코드를 포함합니다. 프로그램을 작성하다 보면, 예외 발생 여부와 관계없이 반드시 수행해야 하는 작업들이 있습니다. 예를 들어, 파일을 열었으면 반드시 닫아야 한다거나, 데이터베이스 연결을 해제해야 하는 작업 등이 그렇습니다. 이러한 ‘정리(cleanup)’ 작업들은 예외 발생 여부와 관계없이 항상 실행되어야 하므로 finally 블록에 위치시킵니다. 저의 경우, 대용량 파일을 처리할 때 open() 함수로 파일을 열고 with 문을 사용하지 않을 때가 간혹 있었는데, 그때마다 예외 처리 후 finally 블록에 file.close()를 넣어 파일이 안전하게 닫히도록 했습니다.
try:
f = open("my_file.txt", "r") # 파일 읽기 모드로 열기
content = f.read()
print(content)
except FileNotFoundError:
print("오류: 파일을 찾을 수 없습니다.")
except Exception as e:
print(f"예상치 못한 오류 발생: {e}")
finally:
# 예외 발생 여부와 관계없이 항상 실행되는 코드
if 'f' in locals() and not f.closed: # 파일 객체가 존재하고 닫히지 않았다면
f.close()
print("파일이 안전하게 닫혔습니다.")
위 예시에서는 my_file.txt 파일을 열고 읽는 작업을 수행합니다. 파일이 없으면 FileNotFoundError가 발생할 수 있고, 다른 문제가 생기면 일반 Exception이 발생할 수 있습니다. 어떤 경우든 finally 블록은 실행되어 열린 파일 객체를 안전하게 닫습니다. 이것은 시스템 자원 누수를 방지하고 프로그램의 견고성을 높이는 중요한 습관입니다. 특히 텍스트 파일 읽기와 쓰기 같은 작업에서 매우 유용합니다.
예외 처리의 실제 활용 예제: 사용자 입력 받기
사용자로부터 올바른 입력을 받을 때까지 반복적으로 요청하는 로직에 try-except를 활용하여 견고한 프로그램을 만들 수 있습니다. 실제 프로젝트에서 사용자 입력은 항상 예상치 못한 값으로 들어올 수 있습니다. 예를 들어, 나이를 숫자로 입력받아야 하는데 사용자가 실수로 문자를 입력하는 경우가 많습니다. 이때 예외 처리를 사용하면 프로그램이 강제로 종료되지 않고, 사용자에게 다시 입력 기회를 주어 원하는 형식의 데이터를 안정적으로 받을 수 있습니다.
while True:
try:
user_age = int(input("당신의 나이를 숫자로 입력해주세요: "))
if user_age < 0:
raise ValueError("나이는 음수가 될 수 없습니다.") # 의도적으로 예외 발생
print(f"입력하신 나이는 {user_age}세입니다.")
break # 올바른 입력 시 반복문 종료
except ValueError as e:
print(f"오류: {e}. 유효한 숫자를 입력해야 합니다. 다시 시도해주세요.")
except Exception as e:
print(f"예상치 못한 오류가 발생했습니다: {e}")
이 코드는 while True 반복문과 try-except 블록을 결합하여, 사용자가 올바른 나이를 입력할 때까지 계속 요청합니다. 만약 숫자가 아닌 값을 입력하면 ValueError가 발생하고, except ValueError 블록이 실행되어 사용자에게 오류 메시지를 보여줍니다. 심지어 나이가 음수인 경우처럼, 프로그램 로직 상 허용되지 않는 값은 raise ValueError를 사용하여 의도적으로 예외를 발생시킬 수도 있습니다. 이렇게 하면 사용자로부터 원하는 형태의 유효한 데이터를 안전하게 확보할 수 있습니다.
FAQ
Q1: try-except를 남용하면 안 되는 이유는 무엇인가요?
try-except를 너무 광범위하게 사용하거나 모든 예외를 한 번에 잡으면, 예상치 못한 버그를 숨기거나 디버깅을 어렵게 만들 수 있습니다. 필요한 곳에만 구체적인 예외 처리를 적용하고, 로직 상 오류가 발생할 수 있는 부분을 명확히 인식하여 처리하는 것이 좋은 습관입니다.
Q2: 예외 처리와 조건문(if문)의 차이는 무엇인가요?
예외 처리는 예상치 못한 '예외적인' 상황에 대처하는 것이고, 조건문은 예상된 '정상적인' 흐름을 제어하는 데 사용됩니다. 예를 들어, 파일이 존재하는지 여부를 확인하고 싶을 때는 os.path.exists() 같은 조건문을 사용하는 것이 더 적절합니다. 하지만 파일이 열리던 도중 알 수 없는 이유로 오류가 발생했다면, 이는 예외 처리의 영역입니다. 저는 파일을 열기 전에 if os.path.exists('file.txt')로 존재 여부를 확인하고, 파일을 열다가 생길 수 있는 권한 문제 등은 try-except로 처리하는 방식을 선호합니다.
Q3: 예외 처리를 하지 않으면 어떤 문제가 생기나요?
예외 처리를 하지 않으면, 예상치 못한 문제가 발생했을 때 프로그램이 사용자에게 아무런 설명 없이 갑자기 종료될 수 있습니다. 이는 사용자 경험을 저하시킬 뿐만 아니라, 중요한 작업 도중 데이터 손실을 야기하거나 시스템에 부하를 줄 수도 있습니다. 특히 서버 프로그램처럼 항상 실행되어야 하는 경우에는 서비스 중단으로 이어질 수 있습니다.
이번 챕터에서는 파이썬의 예외 처리 기본인 try-except-finally 블록에 대해 알아보았습니다. 코드의 특정 부분에서 발생할 수 있는 문제를 미리 예측하고 대비하는 것은 견고하고 안정적인 프로그램을 만드는 데 필수적인 요소입니다. 다음 챕터에서는 다양한 예외 유형에 대해 더 깊이 파고들어, 상황에 맞는 효과적인 예외 처리 전략을 함께 고민해 보겠습니다. 꾸준히 연습하여 예외 처리를 자유자재로 다루는 멋진 바이브 코더가 되시길 바랍니다!
참고 자료
- [1] Python 공식 튜토리얼 — 오류와 예외 — https://docs.python.org/ko/3/tutorial/errors.html


