안녕하세요! 프로그래밍을 하면서 많은 Error Message들을 마주치시게 될 텐데료. 이번에는 Exception을 어떻게 다루는지와 Exception에 대해서 알아보겠습니다. 그럼 시작하겠습니다! :)
Error
우리가 마주치는 Error Message들은 크게 2가지로 분류할 수 있다. 바로 Syntax Error(구문상의 오류)와 Exception이다. 전자는 문법상의 문제를 해결하는 것으로 에러메세지에서 잘못된 부분을 ^를 통해 잘 짚어주기에 비교적 해결하기 쉽다. 후자의 경우 코드가 문법상 문제가 없더라도 실행시 발생하는 오류들을 이야기하며, 이런 Exception이라고 해서 무조건 프로그램에 치명적이지는 않다.
지금부터는 Exception에 초점을 맞춰서 살펴보도록 하겠다. 이러한 Exception에는 무척 다양한 유형이 있으며, 특정 Error와 함께 자세한 에러메세지를 함께 제공한다. 이 때 이 Error Message에는 Exception이 발생한 Context를 Stack 역추적 형식으로 보여주게 된다. 즉 차근차근 코드를 타고올라가 Exceotion의 발생을 역추적하는 방식인 것이다. 아래와 같은 예제를 보면 stack 역추적 방식이 뭔지 느낄 수 있고, Error message를 어떻게 봐야할지에 대한 직관 또한 얻을 수 있을 것이다.
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 10, in Division
ValueError: invalid literal for int() with base 10: 'A'
여기서 우리는 Exception에 대해서 더 자세히 살펴보려고 한다.
Exception
그렇다면 이러한 Exception은 파이썬에서 도대체 어떻게 존재하고 있는 걸까? 파이썬에서 Exception을 수동으로 발생시키는 것들을 본적이 있을 것이다. 그렇다면 이러한 Exception또한 파이썬에서 다루는 객체이어야 할 것이다. ( 파이썬의 모든 것은 객체니까) 그러한 관점에서 Exception에 대해서 정보를 찍어보자.
raise ValueError
dir(ValueError)
>>> [... ,
'__class__',
... ,
'args']
많은 것들이 있지만, 주목해야할 것은 이정도가 아닐까 싶다. 이 Exception객체의 namespace에 __class__
를 가지고 있다. 즉, class 객체인 것이다. 엇 Exception이 class 객체라고? 그렇다면 앞서 공부한 것을 바탕으로 mro()
를 찍어보면 클래스간 상속관계가 나와야 할 것 같다. 파이썬의 객체기에 적어도 object는 보일 것이다.
ValueError.mro()
>>> [ValueError, Exception, BaseException, object]
그렇다. Exception은 class 객체였다. 사실 Python에서 Exception의 종류는 정말 많고, 클래스 객체이기에 상속관계를 따지다 보면 아래와 같이 hierarchical하게 정리할 수 있다. 앞서 syntax error와 Exception을 구분한 것은 좁은 의미에서 Exception을 정의한 것이고, 큰 의미의 Exception에는 모두 포함된다는 것을 체크하자.
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
+-- EncodingWarning
+-- ResourceWarning
Error Message
그렇다면 이러한 Exception 발생시 우리에게 보이는 Error Message와 같은 세부사항은 어떻게 생기는 것일까? 답은 개발자가 직접 적어놓는 것이다. 그렇다면 어떻게 적어놓으면 Exception에 이러한 message를 넣을 수 있을까? 방법은 간단하다. Exception뒤에 (string)
의 형태의 코드를 추가해주는 것이다. 즉, 우리가 지정한 특정 맥락에서의 Exception발생시 이러한 이유로 Exception이 발생했다는 메세지를 전달해줄 수 있는 것이다.
raise NameError('Error Message')
>>>Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: Error Message
Exception Handling
그렇다면 이러한 Exception을 어떻게 처리할까? 이에 대해서 일반적인 Exception Handling에 대한 글들과는 조금 다르게 의견을 피력하겠다. 사실 Exception을 처리하는 관점에서 크게 두가지로 다시 나눌 수 있다고 생각한다. 첫 번째는 논리상의 Exception이다. 이 경우에는 우리가 코드를 작성할 때 logical한 측면에서 발생하는 Exception이기 때문에 개인적으로 logical syntax인 if문 등을 활용해서 이러한 Exception이 발생하지 않게 하는 것이 최우선이라고 생각한다. 두 번째로는 예상이 어려운 Exception이다. 물론 이 또한, 예상가능에 대한 범주를 어떻게 보느냐에 따라 다르겠지만, 우리가 프로그램을 만들다 보면 모든 데이터를 빠짐없이 훑어보는 것은 쉽지않다. 그런데 이러한 데이터속에 문제가 있는 데이터가 일부 있다면? 심지어 그런 데이터들이 비슷한 로직으로 에러를 발생시키는 것이 아니라 각각 개별적이라면 하나씩 제거해줘야 할 것이다. 이러한 Exception에 대해서는 나는 Exception Handling을 위한 구문을 사용하는 것을 추천한다. 즉, 이러한 문제가 발생하더라도 우리의 프로그램이 멈추면 안되기 때문에 이러한 문제가 될 상황들을 미리 대비해놓기 위해서 이러한 Exception Handling을 통해 대처하는 것이다.
try ~ except ~
이러한 Exception Handling을 위해 파이썬에서는 try~ except~
구문을 사용한다. 해당 구문의 동작을 정리해보자.
- try절에 있는 코드블럭이 실행된다.
- try절 내에서 Exception이 발생하지 않는다면 except절을 건너뛰고, 다음 실행으로 넘어간다.
- try절 내에서 Exception이 발생한다면 except절이 실행되고, 다음 실행으로 넘어가게 된다.
- 다만, 이 때 우리가 미리 명명한 Exception과 일치하지않는 Exception이 발생할 경우, handling이 되지 않기 때문에 바로 Exception이 발생하며 중단된다.
- 추가적으로 try except문에서 except절은 하나의 try절에 대해서 여러개 있을 수 있다.
try:
예외 발생 가능 코드
except <Excpetion Type>:
예외 발생시 대응하는 코드
이렇게 try~ except~
문을 통해서 예외 상황을 처리할 수 있는 것이다. except에서 특정 Exception을 명시한 것 또한 위에서 언급한대로다. 이 Exception과 일치하지 않는다면 그대로 중단되며 Exception을 발생시킬 것이다. 따라서 우리가 실행하려는 코드이지만, Exception발생 가능성이 있는 경우 try절에 넣어주고, 예상한 Excpetion이 발생했을 때 어떻게 처리해줄지를 except문에 넣어준다. 우리가 보는 대부분의 에러메세지는 이러한 문법을 통해 만들어져 있어, 특정 에러 발생시 우리에게 전해지는 세부 에러메세지는 다른 개발자분들께서 코딩해놔주신 메세지인 것이다.
while True:
try:
x = int(input("Please enter a number: "))
break
except ValueError:
print("Oops! That was no valid number. Try again...")
출처 : https://docs.python.org/3/tutorial/errors.html
참고로 이 except문에서는 튜플형식을 통해 여러개의 Exception을 함께 명명할 수 있다.
except (RuntimeError, TypeError, NameError):
pass
이러한 try~ except~
문에 조금 더 사족을 붙인 구문들이 존재한다.
try ~ except ~ else
try:
예외 발생 가능 코드
except <Excpetion Type>:
예외 발생시 동작하는 코드
else:
예외가 발생하지 않을 때 동작하는 코드
try ~ except ~ finally
try:
예외 발생 가능 코드
except <Excpetion Type> :
예외 발생시 동작하는 코드
finally:
예외 발생 여부와 상관없이 실행됨
개인적으로 이러한 구문을 많이 쓰지도 않고, 굳이라는 생각을 해서 길게 적지는 않겠다. 알아두면 추후에 타인의 코드를 볼 때 문제는 없을 것 같다.
추가적으로 특정 조건에 만족하지 않을 경우 에러를 발생시키는 assert syntax도 알아두면 좋을 것 같다. 이렇게 Assertion Error
를 발생시키며 assert 문에 ,를 붙이고 뒤에 string을 넣어 Assertion Error
발생시 전달해줄 에러메세지를 적어줄 수도 있다. ( 개인적으로는 어떤 튜토리얼이나 문제를 작성할 때 종종 봤던 것 같다. )
a = 1
b = 2
assert a == b , "Error Message"
>>> AssertionError: Error Message
Exception에 대한 더 깊은 이해
지금까지 배운 것을 바탕으로 한가지 생각해보면 좋을 만한 것에 대해 이야기해보겠다. Exception이 클래스 객체임을 확인했다. 그렇다면, 우리가 Exception자체를 만들 수 있지 않을까 싶다. 한번 해도록 하자. 우선은 그냥 클래스 객체를 만들어서 try~ except~
문을 통해 처리되는지 확인해보겠다.
class B():
pass
class C(B):
pass
class D(C):
pass
for cls in [B, C, D]:
try:
raise cls()
except B:
print("B")
>>> TypeError: catching classes that do not inherit from BaseException is not allowed
안된다. 에러메세지를 살펴보니, BaseException
이라는 클래스를 상속해야만 Exception이 될 수 있는 것 같다. 이를 바탕으로 한번 만들어보자.
class B(Exception):
pass
class C(B):
pass
class D(C):
pass
for cls in [B, C, D]:
try:
raise cls()
except D:
print("D")
except C:
print("C")
except B:
print("B")
>>> B
C
D
이렇게 실제 Excepton처럼 처리되는 것을 볼 수 있다. 결국, Exception은 Basic Exception
클래스를 상속받는 클래스 객체였던 것이다!
References
https://docs.python.org/3/tutorial/errors.html
https://docs.python.org/3/library/exceptions.html
네이버 AI tech BoostCamp week1 최성철 교수님 교안
'Python' 카테고리의 다른 글
Python - configparser & argparser (0) | 2022.02.17 |
---|---|
Python - JSON & Pickle (0) | 2022.02.17 |
Python - File Handling & With Statement (0) | 2022.02.16 |
Python - Module & Project (0) | 2022.02.15 |
Python OOP - Part2 (0) | 2022.02.13 |