예외(exception)는 프로그램 실행 중 발생할 수 있는 오류 상황을 의미한다. 예외가 발생하면 프로그램은 기본적으로 중단된다. 따라서 예상 가능한 오류는 적절히 처리해 프로그램이 갑자기 종료되지 않도록 만들고, 사용자나 개발자에게 필요한 정보를 제공해야 한다.
예외는 실행 중에 발생하는 오류이다. 문법 오류처럼 실행 전에 발견되는 문제와 달리, 예외는 프로그램이 실행되다가 특정 상황에서 발생한다.
print(int(10 / 3))
print(5 / 0)
print(4 / 2)
출력 결과 :
3
Traceback (most recent call last):
...
ZeroDivisionError: division by zero
5/0 에서 ZeroDivisionError 가 발생하면 프로그램은 그 지점에서 중단된다. 따라서 그 아래에 있는 print(4 / 2) 는 실행되지 않는다.
자주 만나는 예외는 다음과 같다.
| 타입 | 발생 상황 | 간단 예제 | 설명 |
|---|---|---|---|
| Exception | 일반 적인 예외 | except Exception | 대부분 예외의 부모 |
| Value Error | 값이 잘못 됨 | int("abc") | 타입은 맞지만 값이 잘못됨 |
| TypeError | 타입이 잘못됨 | "10" + 10 | 연산 불가능한 타입 |
| ZeroDivisionError | 0으로 나눔 | 1 / 0 | 수학적 오류 |
| IndexError | 리스트 범위 초과 | list[5] | 인덱스 범위 오류 |
| KeyError | 딕셔너리 키 없음 | d["b"] | 존재하지 않는 키 |
| AttributeError | 속성 없음 | x.append() | 객체에 없는 메서드 |
| FileNotFoundError | 파일 없음 | open("x.txt") | 파일 경로 오류 |
| ModuleNotFoundError | 모듈 없음 | import x | 모듈 import 실패 |
| StopIteration | 반복 종료 | next(iter([])) | 반복 끝 |
| KeyboardInterrupt | Ctrl + c | 사용자 입력 | 강제 종료 |
| SystemExit | 프로그램 종료 | exit() | 종료 요청 |
파이썬 예외는 클래스 계층 구조를 가진다. 대표적인 구조는 다음과 같다.
BaseException
├── Exception
│ ├── ValueError
│ ├── TypeError
│ ├── ZeroDivisionError
│ ├── IndexError
│ └── ...
├── KeyboardInterrupt
└── SystemExit
일반적으로 우리가 처리하는 예외는 대부분 Exception 의 하위 클래스이다.
| 예외 | 의미 |
|---|---|
| Exception | 코드 실행 중 발생하는 일반적인 오류의 부모 클래스 |
| KeyboardInterrupt | 사용자가 Ctrl + C로 프로그램을 중단할 때 발생 |
| SystemExit | exit() 또는 sys.exit()로 프로그램 종료를 요청할 때 발생 |
KeyboardInterrupt 오 SystemExit 는 일반적인 오류라기보다 프로그램을 중단하거나 종료하기 위한 신호에 가깝다. 그래서 보통은 BaseException 이 아니라 Exception 을 잡는다.
try:
raise KeyboardInterrupt
except Exception:
print("Exception 잡힘")
except BaseException:
print("BaseException 잡힘")
출력 결과 :
BaseException 잡힘
except: 처럼 예외 타입을 생략하면 BaseException 계열까지 잡을 수 있어 위험하다. 사용자의 강제 종료 신호까지 막아버릴 수 있기 때문이다.
예외 처리는 try, except , else , finally 를 사용한다.
try:
예외가 발생할 가능성이 있는 코드
except 예외타입:
해당 예외가 발생했을 때 실행할 코드
else:
예외가 발생하지 않을 때 실행할 코드
finally:
예외 발생 여부와 관계없이 항상 실행할 코드
가장 기본적인 형태는 try-except 이다.
try:
print(10 / 3)
print(5 / 0)
print(4 / 2)
except Exception:
print("예외 발생")
print("프로그램을 종료한다.")
출력 결과 :
3.3333333333333335
예외 발생
프로그램을 종료한다.
try 블록에서 예외가 발생하면 그 즉시 except 블록으로 이동한다. 따라서 print(4 / 2) 는 실행되지 않는다.
특정 예외만 처리할 수도 있다.
try:
print(10 / 3)
print(5 / 0)
print(4 / 2)
except ZeroDivisionError:
print("0으로 나눌 수 없음")
print("프로그램을 종료한다.")
출력 결과 :
3.3333333333333335
0으로 나눌 수 없음
프로그램을 종료한다.
상황에 따라 발생할 수 있는 예외가 다르다면 except 를 여러개 사용할 수 있다.
try:
data = [10, 20, 30, 40, 50]
print(data[0])
print(data[5])
print(data[1])
except IndexError:
print("인덱스 오류")
print("프로그램을 종료한다.")
출력 결과 :
10
인덱스 오류
프로그램을 종료한다.
여러 예외를 나누어 처리하면 오류 원인에 맞는 메시지를 제공할 수 있다.
try:
data = [10, 20, 30, 40, 50]
print(5 / 0)
except IndexError:
print("인덱스 오류")
except ValueError:
print("입력 오류")
except ZeroDivisionError:
print("0으로 나눌 수 없음")
except Exception:
print("예외 발생")
else:
print("에러가 발생하지 않은 정상적인 프로그램")
finally:
print("에러에 관계없이 무조건 실행되는 문장")
print("프로그램을 종료한다.")
출력 결과 :
0으로 나눌 수 있다.
에러에 관계없이 무조건 실행되는 문장
프로그램을 종료한다.
except 는 구체적인 예외부터 작성하는 것이 좋다. Exception 을 가장 위에 쓰면 대부분의 예외가 먼저 잡혀서 아래의 구체적인 except 블록은 실행되지 않는다.
else 와 finallyelse 는 try 블록에서 예외가 발생하지 않았을 때만 실행된다. finally 는 예외 발생 여부와 관계없이 항상 실행된다.
try:
number = int("10")
except ValueError:
print("숫자로 변환할 수 없음")
else:
print("변환 성공:", number)
finally:
print("변환 시도 종료")
출력 결과 :
변환 성공: 10
변환 시도 종료
finally 는 파일 닫기, DB 연결 해제, 임시 리소스 정리처럼 반드시 실행되어야 하는 작업에 사용한다.
except 예외타입 as 변수 형태를 사용하면 발생한 예외 객체를 변수로 받을 수 있다.
try:
int("abc")
except Exception as e:
print("에러 메시지:", e)
print("에러 타입:", type(e))
print("args:", e.args)
출력 결과 :
에러 메시지: invalid literal for int() with base 10: 'abc'
에러 타입: <class 'ValueError'>
args: ("invalid literal for int() with base 10: 'abc'",)
예외 객체에는 오류 메시지, 타입, 추가 정보가 들어있다.
Exception 객체
├── args → 에러 메시지나 전달된 값
├── __str__() → 사용자용 문자열 표현
├── __repr__() → 개발자용 표현
├── __class__ → 예외 타입
└── traceback → 에러 발생 위치 정보
as e 를 사용하는 이유는 발생한 예외 객체의 정보를 활용하기 위해서이다.
raise 로 예외 발생시키기raise 는 의도적으로 예외를 발생시키는 키워드이다. 잘못된 입력이나 비정상적인 상태를 명확히 알릴 때 사용한다.
try:
raise Exception("예외가 발생했어요")
except Exception as e:
print(e)
출력 결과 :
예외가 발생했어요.
def validate_even(number):
if number % 2 == 1:
raise ValueError("홀수를 입력했어요")
print(number)
try:
validate_even(3)
except ValueError as e:
print("예외가 발생:", e)
출력 결과 :
예외가 발생: 홀수를 입력했어요
이처럼 함수 내부에서 문제를 발견하면 raise 로 예외를 발생기키고, 함수를 호출한 쪽에서 처리하게 만들 수 있다.
except 블록 안에서 raise 만 사용하면 현재 처리 중인 예외를 다시 바깥으로 전달한다.
def parse_number(text):
try:
return int(text)
except ValueError:
print("숫자 변환 실패")
raise
try:
parse_number("abc")
except ValueError as e:
print("호출한 쪽에서 다시 처리:", e)
출력 결과 :
숫자 변환 실패
호출한 쪽에서 다시 처리: invalid literal for int() with base 10: 'abc'
예외는 현재 함수에서 처리되지 않으면 호출한 함수 쪽으로 전달된다. 이를 예외 전파라고 한다.
def func1():
func2()
def func2():
func3()
def func3():
print("%d" % "문자열")
try:
func1()
except TypeError:
print("타입이 올바르지 않다.")
출력 결과 :
타입이 올바르지 않다.
func3() 에서 발생한 TypeError 는 func2(), func1() 을 거쳐 최종적으로 바깥의 try-except 에서 처리된다.
함수 내부에서 직접 처리할 수도 있다.
def func1():
func2()
def func2():
func3()
def func3():
try:
print("%d" % "문자열")
except TypeError:
print("타입이 올바르지 않다.")
func1()
출력 결과 :
타입이 올바르지 않다.
어느 위치에서 예외를 처리할지는 프로그램 구조에 따라 달라진다. 보통 예외를 의미 있게 처리할 수 있는 위치에서 잡는 것이 좋다.
예외 처리는 단순히 오류를 숨기는 문법이 아니다. 프로그램이 실패할 수 있는 상황을 명확히 표현하고, 복구 가능한 오류를 적잘히 처리하기 위한 도구이다.
try:
result = 10 / 0
except Exception:
print("문제가 발생했다.")
위 코드는 동작은 하지만 어떤 문제가 발생했는지 알기 어렵다. 가능하면 구체적인 예외를 잡는 것이 좋다.
try:
result = 10 / 0
except ZeroDivisionError:
print("0으로 나눌 수 없다.")
try:
result = 10 / 0
except ZeroDivisionError:
pass
pass 로 예외를 무시하면 문제가 발생했는지 알기 어렵다. 정말 무시해도 되는 상황이 아니라면 로그를 남기거나 사용자에게 안내하는 것이 좋다.
def divide(a, b):
if b == 0:
raise ZeroDivisionError("b는 0이 될 수 없다.")
return a / b
try:
print(divide(10, 0))
except ZeroDivisionError as e:
print(e)
예외 처리는 프로그램이 예상치 못한 상황에서도 무너지지 않도록 도와주는 중요한 문법이다.
| 문법 | 역할 |
|---|---|
| try | 예외가 발생할 가능성이 있는 코드를 감싼다. |
| except | 특정 예외가 발생했을 때 실행할 코드를 작성한다. |
| else | 예외가 발생하지 않았을 때 실행된다. |
| finally | 예외 발생 여부와 관계없이 항상 실행된다. |
| raise | 의도적으로 예외를 발생시키거나 다시 전달한다. |
| as e | 발생한 예외 객체를 변수로 받아 정보를 활용한다. |
좋은 예외 처리는 오류를 숨기는 것이 아니라, 오류가 발생했을 때 프로그램이 어떤 방식으로 대응할지 명확하게 정하는 것이다.
본 정리는 강의 내용과 아래 자료를 참고하여 학습 목적으로 작성하였습니다.