예외(exception)란 코드를 실행하는 과정 중에 발생하는 에러를 뜻한다. 다음과 같이 10을 나누는 함수를 보자.
>>> def ten_div(x):
... return 10 / x
...
다음과 같이 어떠한 값을 10으로 나누는 함수 ten_div
는 인수에 따라서 정상 작동하기도 하고 에러가 나기도 한다. 이 함수에 2을 넣으면 5.0이 나온다.
>>> ten_div(2)
5.0
그런데 만약에 이 함수에 0 을 넣으면 실행하는 도중에 에러가 발생한다. 이러한 상황을 예외라고 하는데 여기에서는 어떤 숫자를 0으로 나누어서 ZeroDivisionError
라는 에러가 발생했다.
>>> ten_div(0)
Traceback (most recent call last):
File "<pyshell#121>", line 1, in <module>
ten_div(0)
File "<pyshell#119>", line 2, in ten_div
return 10 / x
ZeroDivisionError: division by zero
ZeroDivisionError
뿐만 아니라 지금까지 만났던 AttributeError, NameError, TypeError
등 다양한 에러들 모두 예외이다. 이번에는 예외가 발생했을 때 스크립트를 중단하지 않고 계속 실행하게 해주는 예외처리를 살펴보자.
예외 처리를 하려면 다음과 같이 try
에 실행할 코드를 입력하고 except
에 예외 발생시 처리할 코드를 넣은다.
try:
실행할 코드
except:
예외가 발생했을 때 처리하는 코드
이제 숫자를 0으로 나누었을 때 발생하는 예외를 처리해보자.
try:
x = int(input('나눌 숫자를 입력하세요: '))
y = 10 / x
print(y)
except: # 예외가 발생했을 때 실행됨
print('예외가 발생했습니다.')
실행 결과
나눌 숫자를 입력하세요: 0 (입력)
예외가 발생했습니다.
숫자 0을 넣으면 예외가 발생하여 코드를 중단하지 않고 바로 except
로 가서 '예외가 발생했습니다.' 가 출력된다. 즉 try
의 y = 10 / x
를 비롯하여 그 아래줄의 print(y)
도 실행되지 않는다.
이번에는 except
에 예외 이름을 지정하여 특정 예외가 발생했을 때만 처리하는 코드를 만들어보자.
try:
실행할 코드
except 예외이름:
예외가 발생했을 때 처리하는 코드
다음과 같이 정수 두 개를 받아서 하나는 인덱스, 하나는 나눌 값으로 사용한다 그리고 except
로 ZeroDivisionError
와 IndexError
를 지정한다.
y = [10, 20, 30]
try:
index, x = map(int, input('인덱스와 나눌 숫자를 입력하세요: ').split())
print(y[index] / x)
except ZeroDivisionError: # 숫자를 0으로 나눠서 에러가 발생했을 때 실행됨
print('숫자를 0으로 나눌 수 없습니다.')
except IndexError: # 범위를 벗어난 인덱스에 접근하여 에러가 발생했을 때 실행됨
print('잘못된 인덱스입니다.')
이와 같이 하면 만약 x
로 0을 넘겨주면 print('숫자를 0으로 나눌 수 없습니다.')
가 실행되고 index
로 인덱스 범위를 넘는 4 를 넘겨주면 print('잘못된 인덱스입니다.')
가 실행된다.
특히 except
뒤에 as
로 변수를 지정하면 발생한 예외의 에러 메시지를 받아올 수 있다.
try:
실행할 코드
except 예외 as 변수:
예외가 발생했을 때 처리하는 코드
앞에서 만든 코드의 except
뒤에 as e
를 넣는다. 보통 예외(exception)의 앞 글자를 따서 e
로 변수를 지정한다.
y = [10, 20, 30]
try:
index, x = map(int, input('인덱스와 나눌 숫자를 입력하세요: ').split())
print(y[index] / x)
except ZeroDivisionError as e: # as 뒤에 변수를 지정하면 에러를 받아옴
print('숫자를 0으로 나눌 수 없습니다.', e) # e에 저장된 에러 메시지 출력
except IndexError as e:
print('잘못된 인덱스입니다.', e)
실행 결과
인덱스와 나눌 숫자를 입력하세요: 2 0 (입력)
숫자를 0으로 나눌 수 없습니다. division by zero
실행 결과
인덱스와 나눌 숫자를 입력하세요: 3 5 (입력)
잘못된 인덱스입니다. list index out of range
이와 같이 예외가 발생하는 숫자를 넣으면 예외에 해당하는 에러 메시지도 나온다. 단, 예외가 여러개 발생한다면 먼저 발생한 예외의 처리 코드만 실행된다(또는 예외 중에서 높은 예외부터 처리된다. 기반 클래스 > 파생 클래스 순서)
단 모든 예외 메시지를 출력하고 싶다면 다음과 같이 except Exception
을 하고 as
로 변수를 넣으면 된다.
except Exception as e: # 모든 예외의 에러 메시지를 출력할 때는 Exception을 사용
print('예외가 발생했습니다.', e)
Built-in Exceptions - Python 3.10.1 documentation
이번에는 예외가 발생하지 않았을 때 코드를 실행하는 else
에 대해서 알아보자. 다음과 같이 else
는 except
바로 다음에 와야 하며 except
를 생략할 수 없다.
try:
실행할 코드
except:
예외가 발생했을 때 처리하는 코드
else:
예외가 발생하지 않았을 때 실행할 코드
그러면 10을 입력 된 숫자로 나누고 예외가 발생하지 않는다면 계산 결과를 출력해보자.
try:
x = int(input('나눌 숫자를 입력하세요: '))
y = 10 / x
except ZeroDivisionError: # 숫자를 0으로 나눠서 에러가 발생했을 때 실행됨
print('숫자를 0으로 나눌 수 없습니다.')
else: # try의 코드에서 예외가 발생하지 않았을 때 실행됨
print(y)
나눌 숫자를 입력하세요: 2 (입력)
5.0
나눌 숫자를 입력하세요: 0 (입력)
숫자를 0으로 나눌 수 없습니다.
예외가 발생하면 except
부분이 실행되고 아닌 경우 else
부분이 실행된다.
이번에는 예외 발생 여부와 상관없이 실행되는 finally
에 대해서 알아보자. 특히 finally
는 except
와 else
를 생략할 수 있다.
try:
실행할 코드
except:
예외가 발생했을 때 처리하는 코드
else:
예외가 발생하지 않았을 때 실행할 코드
finally:
예외 발생 여부와 상관없이 항상 실행할 코드
다음은 코드가 끝나면 항상 '코드 실행이 끝났습니다.' 라는 문자열이 출력된다.
try:
x = int(input('나눌 숫자를 입력하세요: '))
y = 10 / x
except ZeroDivisionError: # 숫자를 0으로 나눠서 에러가 발생했을 때 실행됨
print('숫자를 0으로 나눌 수 없습니다.')
else: # try의 코드에서 예외가 발생하지 않았을 때 실행됨
print(y)
finally: # 예외 발생 여부와 상관없이 항상 실행됨
print('코드 실행이 끝났습니다.')
나눌 숫자를 입력하세요: 2 (입력)
5.0
코드 실행이 끝났습니다.
나눌 숫자를 입력하세요: 0 (입력)
숫자를 0으로 나눌 수 없습니다.
코드 실행이 끝났습니다.
try
는 함수가 아니므로 스택 프레임을 만들지 않는다. 따라서 try
안에서 변수를 만들어도 밖에서 사용할 수 있다. 물론 except, else, finally
에서도 사용할 수 있다.
지금까지는 0으로 나누었을 때 에러, 인덱스 범위를 넘어서 접근했을 때 에러 등 파이썬에서 예외만 처리했다. 이번에는 우리가 직접 예외를 발생시켜보자.
예외를 발생시킬 때는 raise
에 예외를 지정하고 에러 메시지를 넣는다. 메시지는 생략할 수 있다.
raise 예외('에러메시지')
try:
x = int(input('3의 배수를 입력하세요: '))
if x % 3 != 0: # x가 3의 배수가 아니면
raise Exception('3의 배수가 아닙니다.') # 예외를 발생시킴
print(x)
except Exception as e: # 예외가 발생했을 때 실행됨
print('예외가 발생했습니다.', e)
실행 결과
3의 배수를 입력하세요: 5 (입력)
예외가 발생했습니다. 3의 배수가 아닙니다.
x % 3 != 0
조건에 만족하면 예외가 일어나도록 하였다. 참고로 위에서는 예외로 Exception
을 사용했는데
RuntimeError, NotImplementedError
등 다른 예외를 사용해도 상관없다.
다음은 함수 안에서 raise
를 사용하지만 함수 안에 try except
가 없는 상태이다.
def three_multiple():
x = int(input('3의 배수를 입력하세요: '))
if x % 3 != 0: # x가 3의 배수가 아니면
raise Exception('3의 배수가 아닙니다.') # 예외를 발생시킴
print(x) # 현재 함수 안에는 except가 없으므로
# 예외를 상위 코드 블록으로 넘김
try:
three_multiple()
except Exception as e: # 하위 코드 블록에서 예외가 발생해도 실행됨
print('예외가 발생했습니다.', e)
실행 결과
3의 배수를 입력하세요: 5 (입력)
예외가 발생했습니다. 3의 배수가 아닙니다.
three_multiple
함수 안에서 try except
가 없는 상태에서 raise
로 예외를 발생시켰다. 이렇게 되면 함수 바깥에 있는 except
에서 예외를 처리한다. 즉, 예외가 발생하더라도 현재 블록에서 예외를 처리해줄 except
가 없다면 처리가 될 때 까지 그 상위 코드 블럭으로 올라간다.
만약 함수 바깥쪽에서 예외를 처리해줄 except
가 없다면 코드 실행을 중지하고 에러가 표시 된다.
이번에는 try except
에서 처리한 예외를 다시 발생시키는 방법이다. except
안에서 raise
를 사용하여 현재 예외를 다시 발생시킨다(re-raise)
다음은 three_multiple
코드 블럭의 예외를 다시 발생시킨 뒤에 상위 코드 블럭에서 예외를 처리한다.
def three_multiple():
try:
x = int(input('3의 배수를 입력하세요: '))
if x % 3 != 0: # x가 3의 배수가 아니면
raise Exception('3의 배수가 아닙니다.') # 예외를 발생시킴
print(x)
except Exception as e: # 함수 안에서 예외를 처리함
print('three_multiple 함수에서 예외가 발생했습니다.', e)
raise # raise로 현재 예외를 다시 발생시켜서 상위 코드 블록으로 넘김
try:
three_multiple()
except Exception as e: # 하위 코드 블록에서 예외가 발생해도 실행됨
print('스크립트 파일에서 예외가 발생했습니다.', e)
실행 결과
3의 배수를 입력하세요: 5 (입력)
three_multiple 함수에서 예외가 발생했습니다. 3의 배수가 아닙니다.
스크립트 파일에서 예외가 발생했습니다. 3의 배수가 아닙니다.
three_multiple
함수 안에서 예외를 처리한 다음에 raise
를 사용하여 동일한 예외를 상위 코드 블럭으로 넘겨 함수 바깥쪽 except
에서 예외를 한번 더 처리해줬다.
참고로 raise
만 사용하면 같은 예외를 상위 코드 블럭으로 넘기지만 raise
에 다른 예외를 지정하고 메시지도 넣을 수 있다.
if x % 3 != 0:
raise Exception('3의 배수가 아닙니다.')
print(x)
except Exception as e:
print('three_multiple 함수에서 예외가 발생했습니다.', e)
raise RuntimeError('three_multiple 함수에서 예외가 발생했습니다.')
예외를 발생시키는 방법으로 assert
를 사용하는 방법이 있다. assert
는 지정된 조건식이 거짓일 때 AssertionError
예외를 발생시키며 조건이 참이면 그냥 넘어간다. 보통 나와서는 안되는 조건을 검사할 때 사용한다.
다음은 3의 배수가 아니면 예외가 발생하는 식이다.
x = int(input('3의 배수를 입력하세요: '))
assert x % 3 == 0, '3의 배수가 아닙니다.' # 3의 배수가 아니면 예외 발생, 3의 배수이면 그냥 넘어감
print(x)
assert
는 디버깅 모드에서만 사용가능하다. 특히 파이썬에서는 기본적으로 디버깅 모드이고 assert
가 실행되지 않게 하기 위해선 파이썬에 -O
(대문자 O) 옵션을 추가하면된다.
python -O 스크립트파일.py
지금까지는 기본적으로 파이썬에 내장된 예외를 사용했는데 이번에는 직접 예외를 만들어서 사용해보자. 프로그래머가 직접 만든 예외를 사용자 정의 예외라고 한다.
예외를 만드는 방법은 간단하다. 그냥 Exception
을 상속 받는 새로운 클래스를 만들면 된다. 그리고 __init__
메서드를 호출하고 괄호 안에 에러메시지를 넣어주면 된다.
class 예외이름(Exception):
def __init__(self):
super().__init__('에러메시지')
그러면 입력된 숫자가 3의 배수가 아닐 때 발생시킬 예외를 만들어보자.
class NotThreeMultipleError(Exception): # Exception을 상속받아서 새로운 예외를 만듦
def __init__(self):
super().__init__('3의 배수가 아닙니다.')
def three_multiple():
try:
x = int(input('3의 배수를 입력하세요: '))
if x % 3 != 0: # x가 3의 배수가 아니면
raise NotThreeMultipleError # NotThreeMultipleError 예외를 발생시킴
print(x)
except Exception as e:
print('예외가 발생했습니다.', e)
three_multiple()
실행 결과
3의 배수를 입력하세요: 5 (입력)
예외가 발생했습니다. 3의 배수가 아닙니다.
먼저 Exception
을 상속 받아서 NotThreeMultipleError
라는 예외를 만들었다. 그리고 기반 클래스의 __init__
안에 에러 메시지를 넣어줬다. 이제 raise
로 새롭게 만든 예외를 넘겨주면 된다.
참고로 super().__init__('3의 배수가 아닙니다.')
이와 같이 메시지를 입력하지 않고 그냥
class NotThreeMultipleError(Exception):
pass
이와 같이 아무것도 안해줘도 된다. 그리고 raise
를 사용할 때 에러 메시지를 넣어주면 된다.
raise NotThreeMultipleError('3의 배수가 아닙니다.') # 예외를 발생시킬 때 에러 메시지를 넣음
지금까지 예외 처리에 대해서 배웠다. 예외 처리를 사용하면 에러가 발생해도 스크립트가 중단되지 않고 진행된다.