26. 예외 처리(Exception Handling)
1. 키워드
- 예외 처리(Exception Handling)
- 예외 계층(Exception Hierarchy)
- 사용자 정의 예외
2. 예외 처리 사용하기
- 예외란 코드를 실행하는 중에 발생한 에러를 뜻한다.
- 다음과 같이
10
을 어떤 값으로 나누는 함수ten_div
가 있을 때 인수에 따라 정상적으로 동작하기도 하고 에러가 나기도 한다.
- 이 함수에
2
를 넣으면5.0
이 나온다.
- 하지만
0
을 넣으면 실행하는 중에 에러가 발생한다. - 이런 상황을 예외라고 하는데 여기서는 어떤 숫자를
0
으로 나누어서ZeroDivisionError
예외가 발생했다.
ZeroDivisionError
뿐만 아니라 지금까지 만난AttributeError
,NameError
,TypeError
등 다양한 에러들도 모두 예외이다.
3. try except
로 사용하기
- 예외 처리를 하려면 다음과 같이
try
에 실행할 코드를 넣고except
에 예외가 발생했을 때 처리하는 코드를 넣는다.
- 이제 숫자를
0
으로 나누었을 때 발생하는 예외를 처리해 보자.
- 소스 코드를 실행한 뒤
0
을 입력하고 엔터 키를 누른다.
- 숫자를
0
으로 나누면ZeroDivisionError
예외가 발생한다. - 여기서
except
에서 예외 처리를 하도록 만들었으므로"예외가 발생했습니다."
가 출력된다. - 특히 예외가 발생하면 해당 줄에서 코드 실행을 중단하고 바로
except
로 가서 코드를 실행한다. - 즉,
try
의y = 10 / x
를 비롯하여 그 아래줄에 있는print(y)
도 실행되지 않는다.
- 다시 소스 코드를 실행한 뒤
2
를 입력하고 엔터 키를 누른다.
2
를 입력하니 예외가 발생하지 않고 계산 결과가 잘 출력된다.- 이처럼
try
의 코드가 에러없이 잘 실행되면except
의 코드는 실행되지 않고 그냥 넘어간다. - 즉,
try
의 코드에서 에러가 발생했을 때만except
의 코드가 실행된다.
1) 특정 예외만 처리하기
- 이번에는
except
에 예외 이름을 지정해서 특정 예외가 발생했을 때만 처리 코드를 실행하도록 만들어보자.
- 다음과 같이 정수 두 개를 입력받아서 하나는 리스트의 인덱스로 사용하고, 하나는 나누는 값으로 사용한다.
- 그리고
except
를 두 개 사용하고 각각ZeroDivisionError
와IndexError
를 지정한다.
y = [10, 20, 30]
try:
index, x = map(int, input("인덱스와 나눌 숫자를 입력하세요: ").split())
print(y[index] / x)
except ZeroDivisionError:
print("숫자를 0으로 나눌 수 없습니다.")
except IndexError:
print("잘못된 인덱스입니다.")
- 소스 코드를 실행한 뒤
2 0
을 입력하고 엔터 키를 누른다.
2 0
을 입력하면10 / 0
이 되므로 숫자를0
으로 나누게 된다.이때는 except ZeroDivisionError:
의 처리 코드가 실행된다.
- 다시 소스 코드를 실행한 뒤
3 5
를 입력하고 엔터 키를 누른다.
y = [10, 20, 30]
은 요소가3
개 들어있는 리스트이다.- 따라서 인덱스에
3
을 지정하면 범위를 벗어나게 되며, 이때는except IndexError:
의 처리 코드가 실행된다.
2) 예외의 에러 메시지 받아오기
- 특히
except
에서as
뒤에 변수를 지정하면 발생한 예외의 에러 메시지를 받아올 수 있다.
- 앞에서 만든 코드의
except
에as e
를 넣는다.
y = [10, 20, 30]
try:
index, x = map(int, input("인덱스와 나눌 숫자를 입력하세요: ").split())
print(y[index] / x)
except ZeroDivisionError as e:
print("숫자를 0으로 나눌 수 없습니다.", e)
except IndexError as e:
print("잘못된 인덱스입니다.", e)
2 0
,3 5
처럼 예외가 발생하는 숫자를 넣어보면 해당 예외에 해당하는 에러 메시지가 출력된다.- 단, 예외가 여러 개 발생하더라도 먼저 발생한 예외의 처리 코드만 실행된다.
- 또는 예외 중에서 높은 계층의 예외부터 처리된다.
- 참고로 모든 예외의 에러 메시지를 출력하고 싶다면 다음과 같이
except
에Exception
을 지정하고as
뒤에 변수를 넣으면 된다.
- 이처럼 예외 처리는 에러가 발생하더라도 스크립트의 실행을 중단시키지 않고 계속 실행하고자 할 때 사용한다.
예외 계층
- 예외도 클래스 상속으로 구현되며 다음과 같은 계층으로 이루어져 있다.
- 보통 파이썬에서 새로운 예외를 만들 때는
Exception
을 상속받아서 구현한다.
- 파이썬 예외 계층도는 다음과 같다.
4. else
와 finally
사용하기
- 이번에는 예외가 발생하지 않았을 때 코드를 실행하는
else
를 사용해 보자.
- 다음과 같이
else
는except
바로 다음에 와야 하며except
를 생략할 수는 없다.
- 그럼
10
을 입력된 숫자로 나누고 예외가 발생하지 않으면 계산 결과를 출력해 보자.
try:
x = int(input("나눌 숫자를 입력하세요: "))
y = 10 / x
except ZeroDivisionError:
print("숫자를 0으로 나눌 수 없습니다.")
else:
print(y)
- 소스 코드를 실행한 뒤
2
를 입력하고 엔터 키를 누른다.
2
를 입력했으므로y = 10 / x
에서 예외가 발생하지 않았습니다.- 따라서
else
의 코드가 실행되고 계산 결과가 출력된다.
- 물론 다음과 같이
0
을 입력해서 예외가 발생하면except
의 코드만 실행되고else
의 코드는 실행되지 않는다.
1) 예외와는 상관없이 항상 코드 실행하기
- 이번에는 예외 발생 여부와 상관없이 항상 코드를 실행하는
finally
를 사용해 보자. - 특히
finally
는except
와else
를 생략할 수 있다.
- 다음은
try
의 코드가 끝나면 항상"코드 실행이 끝났습니다."
를 출력한다.
try:
x = int(input("나눌 숫자를 입력하세요: "))
y = 10 / x
except ZeroDivisionError:
print("숫자를 0으로 나눌 수 없습니다.")
else:
print(y)
finally:
print("코드 실행이 끝났습니다.")
- 소스 코드를 실행한 뒤
2
를 입력하고 엔터 키를 누른다.
2
를 입력하여 예외가 발생하지 않았으므로 계산 결과가 출력되고,"코드 실행이 끝났습니다."
도 출력된다.
- 다시 소스 코드를 실행한 뒤
0
을 입력하고 엔터 키를 누른다.
- 숫자를
0
으로 나눠서 예외가 발생했지만finally
는 항상 실행되므로"코드 실행이 끝났습니다."
가 출력된다.
try
,except
,else
,finally
의 실행 과정을 그림으로 나타내면 다음과 같은 모양이 된다.
try
안에서 만든 변수는 try
바깥에서 사용할 수 있나요?
try
는 함수가 아니므로 스택 프레임을 만들지 않는다.- 따라서
try
안에서 변수를 만들더라도try
바깥에서 사용할 수 있다. - 물론
except
,else
,finally
에서도 사용할 수 있다.
5. 예외 발생시키기
- 지금까지 숫자를
0
으로 나눴을 때 에러, 리스트의 범위를 벗어난 인덱스에 접근했을 때 에러 등 파이썬에서 정해진 예외만 처리했다.
- 예외를 발생시킬 때는
raise
에 예외를 지정하고 에러 메시지를 넣는다.
try:
x = int(input("3의 배수를 입력하세요: "))
if x % 3 != 0:
raise Exception("3의 배수가 아닙니다.")
print(x)
except Exception as e:
print("예외가 발생했습니다.", e)
- 소스 코드를 실행한 뒤
5
를 입력하고 엔터 키를 누른다.
5
는3
의 배수가 아니므로raise Exception("3의 배수가 아닙니다.")
로 예외를 발생시켰다.- 이때
Exception
에 넣은 에러 메시지는except Exception as e:
의e
에 들어간다. - 그리고
raise
로 예외를 발생시키면raise
아래에 있는 코드는 실행되지 않고 바로except
로 넘어간다. - 따라서
try
의print(x)
는 실행되지 않는다. - 참고로 이 예제에서는 예외로
Exception
을 사용했는데,RuntimeError
,NotImplementedError
등 다른 예외를 사용해도 상관없다.
1) raise
의 처리 과정
- 이번에는
raise
의 처리 과정을 알아보자.
- 다음은 함수 안에서
raise
를 사용하지만 함수 안에는try except
가 없는 상태이다.
def three_multiple():
x = int(input("3의 배수를 입력하세요: "))
if x % 3 != 0:
raise Exception("3의 배수가 아닙니다.")
print(x)
try:
three_multiple()
except Exception as e:
print("예외가 발생했습니다", e)
- 소스 코드를 실행한 뒤
5
를 입력하고 엔터 키를 누른다.
three_multiple
함수는 안에try except
가 없는 상태에서raise
로 예외를 발생시켰다.- 이렇게 되면 함수 바깥에 있는
except
에서 예외가 처리된다. - 즉, 예외가 발생하더라도 현재 코드 블록에서 처리해 줄
except
가 없다면except
가 나올 때까지 계속 상위 코드 블록으로 올라간다 - 만약 함수 바깥에도 처리해 줄
except
가 없다면 코드 실행은 중지되고 에러가 표시된다.
- 다음은 직접
three_multiple
함수를 호출했으므로except
가 없는 상태이다.
2) 현재 예외를 다시 발생시키기
- 이번에는
try except
에서 처리한 예외를 다시 발생시키는 방법이다.
- 다음과 같이
except
안에서raise
를 사용하면 현재 예외를 다시 발생시킨다.
- 다음은
three_multiple
코드 블록의 예외를 다시 발생시킨 뒤 상위 코드 블록에서 예외를 처리한다.
def three_multiple():
try:
x = int(input("3의 배수를 입력하세요: "))
if x % 3 != 0:
raise Exception("3의 배수가 아닙니다.")
print(x)
except Exception as e:
print("three_multiple 함수에서 예외가 발생했습니다.", e)
raise
try:
three_multiple()
except Exception as e:
print("스크립트 파일에서 예외가 발생했습니다.", e)
- 소스 코드를 실행한 뒤
5
를 입력하고 엔터 키를 누른다.
3의 배수를 입력하세요: 5 (입력)
three_multiple 함수에서 예외가 발생했습니다. 3의 배수가 아닙니다.
스크립트 파일에서 예외가 발생헀습니다. 3의 배수가 아닙니다.
three_multiple
함수 안에서 발생한 예외를 함수 안의except
에서 한 번 처리하고,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
를 사용하는 방법도 있다.
assert
는 지정된 조건식이 거짓일 때AssertionError
예외를 발생시키며 조건식이 참이면 그냥 넘어간다.
- 다음은
3
의 배수가 아니면 예외 발생,3
의 배수이면 그냥 넘어간다.
- 보통
assert
는 나와서는 안 되는 조건을 검사할 때 사용한다.
assert
는 디버깅 모드에서만 실행된다.- 특히 파이썬은 기본적으로 디버깅 모드(
__debug__
의 값이True
)이며assert
가 실행되지 않게 하려면python
에-O
옵션을 붙여서 실행한다.
6. 예외 만들기
- 지금까지 파이썬에 내장된 예외를 처리했는데, 이번에는 예외를 직접 만들어서 발생시켜보자.
- 프로그래머가 직접 만든 예외를 사용자 정의 예외라고 한다.
- 예외를 만드는 방법은 간단한데, 그냥
Exception
을 상속받아서 새로운 클래스를 만들면 된다. - 그리고
__init__
메서드에서 기반 클래스의__init__
메서드를 호출하면서 에러 메시지를 넣어주면 된다.
- 그럼 입력된 숫자가
3
의 배수가 아닐 때 발생시킬 예외를 만들어보자.
class NotThreeMultipleError(Exception):
def __init__(self):
super(NotThreeMultipleError, self).__init__("3의 배수가 아닙니다.")
def three_multiple():
try:
x = int(input("3의 배수를 입력하세요: "))
if x % 3 != 0:
raise NotThreeMultipleError
print(x)
except Exception as e:
print("예외가 발생했습니다.", e)
three_multiple()
5
를 입력하면3
의 배수가 아니므로NotThreeMultipleError
예외가 발생한다.
- 먼저
Exception
을 상속받아서NotThreeMultipleError
예외를 만들었다. - 그리고
__init__
메서드 안에서 기반 클래스의__init__
메서드를 호출하면서 에러 메시지를 넣었다.
class NotThreeMultipleError(Exception):
def __init__(self):
super(NotThreeMultipleError, self).__init__("3의 배수가 아닙니다.")
- 예외를 발생시킬 때는
raise NotThreeMultipleError
와 같이raise
에 새로 만든 예외를 지정해 주면 된다.
- 참고로 다음과 같이
Exception
만 상속받고pass
를 넣어서 아무것도 구현하지 않아도 된다.
- 이때는 예외를 발생시킬 때 에러 메시지를 넣어주면 된다.
- 예외 처리는 에러가 발생하더라도 스크립트의 실행을 중단하지 않고 계속 실행하고자 할 때 사용한다.