파이썬 Error 처리
12 Jan 2020 | Exception Handling Python Standard Library목차
1. Introduction
파이썬에서 에러를 처리하고 관리하는 데에는 다양한 이유가 있다. 실제 Applicaion 상에서 에러가 발생하지 않도록 개발과 테스트 단계에서 미리 에러를 식별하고 수정하는 것은, 어떤 프로그램을 만들 때 굉장히 중요한 과정이라고 할 수 있다.
기본적으로 파이썬에서는 BaseException이라는 class를 통해 에러를 관리하도록 도와준다. 이 class는 모든 내장 exception들의 base class이다. 만약 사용자가 직접 에러 class를 만들고 싶을 때는 이 에러를 사용하는 것이 아니라 Exception class를 사용해야 한다.
코딩을 하다보면 여러 종류의 에러를 보았을 것이다. 예를 들어 아래와 같은 에러가 대표적일 것이다.
ValueError
AssertionError
FileNotFoundError
SyntaxError
대체 이 에러들은 다 어떻게 만들어지고, 어떻게 구성되는 것일까? 사실 이 에러들은 앞서 설명한 BaseException class의 하위 class로 이루어진다. 그 전체 구조는 아래와 같다.
BaseException
+-- SystemExit
+-- KeyboardInterrupt
+-- GeneratorExit
+-- Exception
+-- StopIteration
+-- StopAsyncIteration
+-- ArithmeticError
| +-- FloatingPointError
| +-- OverflowError
| +-- ZeroDivisionError
+-- AssertionError
+-- AttributeError
+-- BufferError
+-- EOFError
+-- ImportError
| +-- ModuleNotFoundError
+-- LookupError
| +-- IndexError
| +-- KeyError
+-- MemoryError
+-- NameError
| +-- UnboundLocalError
+-- OSError
| +-- BlockingIOError
| +-- ChildProcessError
| +-- ConnectionError
| | +-- BrokenPipeError
| | +-- ConnectionAbortedError
| | +-- ConnectionRefusedError
| | +-- ConnectionResetError
| +-- FileExistsError
| +-- FileNotFoundError
| +-- InterruptedError
| +-- IsADirectoryError
| +-- NotADirectoryError
| +-- PermissionError
| +-- ProcessLookupError
| +-- TimeoutError
+-- ReferenceError
+-- RuntimeError
| +-- NotImplementedError
| +-- RecursionError
+-- SyntaxError
| +-- IndentationError
| +-- TabError
+-- SystemError
+-- TypeError
+-- ValueError
| +-- UnicodeError
| +-- UnicodeDecodeError
| +-- UnicodeEncodeError
| +-- UnicodeTranslateError
+-- Warning
+-- DeprecationWarning
+-- PendingDeprecationWarning
+-- RuntimeWarning
+-- SyntaxWarning
+-- UserWarning
+-- FutureWarning
+-- ImportWarning
+-- UnicodeWarning
+-- BytesWarning
+-- ResourceWarning
굉장히 많다. 이 에러와 경고(Warning)들을 다 외우고 있을 필요는 없을 것이다. 하지만 인지는 하고 있는 편이 좋다.
2. Exception 처리: try, except, finally
2.1. 일반적인 처리
try 블록을 수행하는 과정에서 에러가 발생하면 except 블록이 수행된다. 만약 에러가 발생하지 않았다면, except 블록은 수행되지 않는다. 만약 에러의 발생 유무와 상관없이 꼭 어떤 과정을 수행하고 싶다면 finally 블록에 이를 담으면 된다.
# 예시 1
try:
import nothing
except ImportError as error:
print(error)
finally:
import numpy as np
print(np.array([1, 2]))
No module named 'nothing'
[1 2]
# 예시 2
try:
print(3/0)
except ZeroDivisionError:
print("Error: You cannot divide integer by zero")
Error: You cannot divide integer by zero
참고로 assert 조건, "에러 메시지" 인 assert 구문을 통해 에러를 관리할 수도 있다.
2.2. 특별한 요청
아래에는 위와는 다르게 조금은 특별한(?) 요청을 하고 싶을 때 사용할 수 있는 기능들이다.
- 만약 에러를 그냥 회피하고 싶다면 except 블록에
pass를 입력하면 된다. Exception이 발생하였을 때 프로그램을 중단하고 싶으면raise SystemExit을 except 블록에 입력하면 된다.Exception을 일부러 발생하고 싶을 때에도raise구문을 사용하면 된다.
3번 째 경우에 대한 예시를 첨부하겠다. BaseBandit이라는 부모 class가 있고, 사용자는 이 부모 class를 상속받아 TalkativeBandit이라는 자식 class를 만들고 싶다고 하자.
그런데 이 때, 자식 class에 반드시 operate이란 메서드를 구현하도록 미리 설정을 해두고 싶다. 모니터 구석에 메모를 해두는 것 외에 방법이 없을까? 이 때 부모 class인 BaseBandit에 미리 아래와 같은 코드를 구현해 놓으면 원하는 바를 쟁취할 수 있을 것이다.
# 부모 class 구현
class BaseBandit:
def operate(self):
raise NotImplementedError
# 자식 class 구현
class TalkativeBandit(BaseBandit):
def stay(self):
print("Don't talk")
tb = TalkativeBandit()
# 자식 class에서는 operate 메서드를 구현하지 않았으므로
# 부모 class의 operate 메서드가 호출된다.
tb.operate()
# 에러가 발생한다.
Traceback (most recent call last):
File "C:\Users\...\interactiveshell.py", line 2961, in run_code
exec(code_obj, self.user_global_ns, self.user_ns)
File "<ipython-input-17-fdf0f46c74b7>", line 1, in <module>
tb.operate()
File "<ipython-input-12-af85936c9668>", line 3, in operate
raise NotImplementedError
NotImplementedError
operate 메서드를 제대로 구현한다면, 별 문제 없이 코드를 진행할 수 있을 것이다.
3. Exception 추적
바로 위의 예시를 보자. Traceback (most recent call last)란 문구를 볼 수 있을 것이다. 이는 Exception을 역으로 추적한다는 뜻이다.
사용자가 직접 추적 과정을 만들고 싶을 때 stack trace를 표시하고 출력하는 traceback 모듈과 로그 기록을 관리하는 logging 모듈을 사용하면 편리하다.
가장 기초적인 추적 방법은 아래와 같다.
import traceback
try:
tuple()[0]
except IndexError:
print("--- Exception Occured ---")
traceback.print_exc(limit=1)
# 출력 결과
--- Exception Occured ---
Traceback (most recent call last):
File "<ipython-input-19-0acccd16d042>", line 2, in <module>
tuple()[0]
IndexError: tuple index out of range
빈 튜플에 indexing을 시도했으므로 에러가 발생하는 것은 당연하다.
그 에러는 IndexError 인데, 우리는 traceback.print_exc 메서드를 통해 stack trace 정보를 출력할 수 있다.
limit=None이 기본이며 이 때는 제한 없이 stack trace를 출력한다. 위 예시와 같이 1을 입력하면 단 한 개의 stack trace 정보를 출력한다는 뜻이다. file, chain argument 설정을 통해 파일 출력 위치를 설정하거나 연쇄적인 Exception 출력 설정을 관리할 수 있다.
왜 이런 과정을 거쳐야 할까? 만약 이와 같이 try-except를 통해 Exception을 관리해주지 않는다면, 우리는 모든 에러를 잡기 전까지 프로그램 전체를 돌릴 수 없을 것이다.
이번에는 logging 모듈과 합작하여 Exception을 추적해보자.
import traceback
import logging
logging.basicConfig(filename="example.log", format="%(asctime)s %(levelname)s %(message)s")
try:
tuple()[0]
except IndexError:
logging.error(traceback.format_exc())
raise
# 출력 결과
Traceback (most recent call last):
File "C:\Users\...\interactiveshell.py", line 2961, in run_code
exec(code_obj, self.user_global_ns, self.user_ns)
File "<ipython-input-18-16da8da0daa5>", line 6, in <module>
tuple()[0]
IndexError: tuple index out of range
logging 모듈을 통해 우리는 example.log라는 파일에 에러에 관한 기록을 해둘 수 있었다.
이 파일에는 다음과 같은 로그 기록이 남아있다.
2020-01-12 18:38:50,633 ERROR Traceback (most recent call last):
File "<ipython-input-18-16da8da0daa5>", line 6, in <module>
tuple()[0]
IndexError: tuple index out of range
4. Exception 만들기
Exception class 상속을 통해 Exception을 직접 만들 수 있다.
import numpy as np
class SizeError(Exception):
# 에러 메시지를 출력하고 싶으면 아래와 같은 특별 메서드를 구현해야 한다.
def __str__(self):
return "Size does not fit"
# 기준이 되는 base
base = np.eye(3)
# 비교대상인 data
data1 = np.array([[1,2], [3,4]])
data2 = np.ones((3, 3))
# np.array의 shape을 비교하는 함수이다.
def compare(base ,data):
if base.shape != data.shape:
raise SizeError()
else:
print("All Clear")
# 첫 번째 테스트
compare(base=base, data=data1)
# 첫 번째 결과
Traceback (most recent call last):
File "C:\Users\...\interactiveshell.py", line 2961, in run_code
exec(code_obj, self.user_global_ns, self.user_ns)
File "<ipython-input-36-c1718418c4b8>", line 1, in <module>
compare(base=base, data=data1)
File "<ipython-input-35-8ec7197ddfb7>", line 3, in compare
raise SizeError()
SizeError: Size does not fit
# 두 번째 테스트
compare(base=base, data=data2)
# 두 번째 결과
All Clear