파이썬 Error 처리
12 Jan 2020 | References 파이썬목차
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