Python 예외 처리

2020년 7월 3일 수정

이 글은 Python 예외 처리(Exception Handling)의 가장 기초적인 부분을 다룬다. 참고로 문법은 버전 3.x 이후이며 2.x 버전에서는 조금 다를 수 있다.

예외 처리하기

Python 예외 처리의 가장 기본은 tryexcept 문이다. 대충 아래와 같은 식으로 쓰인다.

try:
    do_what()
except:
    print("ERROR: 뭔가 에러가 났어요!")

try 블록 내부에서 예외가 발생할지도 모를 코드를 실행시키고, 만약 여기 안에서 예외가 발생하면 except 블록의 구문이 실행된다.

만약 모든 예외가 아니라 특정 예외만 잡고 싶다면 except 에 정확한 이름을 줄 수 있다.

try:
    do_what()
except ValueError:
    print("Value Error!")

이렇게 하면 do_what() 에서 ValueError 를 제외한 예외는 except 로 진입하지 않는다.

만약 예외사항의 메시지를 알고 싶다면 그대로 출력해 보는 방법이 있다.

try:
    do_what()
except ValueError as e:
    print(f"Value Error: {e}")

위의 경우 ValueError 라는 예외에서 넘겨주는 문자열이 있을 경우 화면에 표시가 된다. 다만 이 부분은 예외 클래스에 따라 좀 더 적합한 방식이 있을 수도 있다.

만약 한 예외가 아니라 여러 예외를 처리하고 싶다면 아래와 같이 except 를 계속 나열하면 된다.

try:
    do_what()
except ValueError:
    print("Value Error!")
except AttributeError:
    print("Attribute Error!")

이렇게 하면 발생한 예외에 맞는 except 블록으로 진입한다.

혹은 여러 예외를 하나의 except 로 잡을 수도 있다.

try:
    do_what()
except (ValueError, AttributeError):
    print("Value or Attribute Error!")

이 경우 ValueError 혹은 AttributeError 둘 중 하나의 예외가 발생하면 except 안의 구문이 실행된다.

위의 경우도 당연히 상세한 내용을 찍어볼 수 있다.

try:
    do_what()
except (ValueError, AttributeError) as e:
    print(f"Something Error: {e}")

물론 이건 예외 클래스에서 출력을 지원할 때의 이야기지만 말이다. 대부분의 기본 예외 오류는 메시지를 다 알려주긴 한다.

예외 발생시키기

예외는 raise 커맨드로 발생시킬 수 있다.

raise ValueError()

보통은 첫 인자로 문자열 메시지를 넣을 수 있다.

raise ValueError("The value is invalid")

사용자 예외 클래스

개인이 만든 기능에는 당연히 개인이 정의한 오류가 발생할 수 있다. 따라서 당연하게도 예외 클래스 또한 원하는 대로 별도로 디자인할 수 있다.

아래는 TestException 이라는 예외를 만들고 이를 일부러 발생시키는 예제다.

class TestException(Exception):
    pass

def test():
    raise TestException()

try:
    test()
except Exception as e:
    print(f'Exception: {e}')

TestException 이 직접 만든 예외이다. 보다시피 Exception 이라는 클래스를 상속받은 클래스다.

위 예제를 실행하면 예외가 발생하면서 하단의 print 문이 그대로 실행된다. except 에서 굳이 TestException 이 아닌 Exception 을 잡은 것은 이 커스텀 예외도 다른 일반 예외와 별 다른 것이 없다는 것을 알 수 있게 하기 위함이다.

그런데 위 코드는 아래 처럼 정확히 무슨 예외인지 표시되지는 않는다.

Exception:

이건 TestException 클래스에서 특별한 정보를 알려주지 않아서이다.

만약 발생한 예외의 정보를 문자열로 찍어야 하는 경우라면 __str__ 메서드를 구현하면 어느 정도 해결할 수 있다. 이 메서드는 문자열 변환 요구 시 호출된다.

class TestException(Exception):
    def __str__(self):
        return "TestException"

TestException 클래스를 위 처럼 수정하면 이제 print를 통해서 TestException 에 의한 예외가 났다는 것을 볼 수 있다.

Exception: TestException

이제 예외의 종류를 화면으로 볼 수 있게 되었다.

하지만 이 정도로는 그다지 유용한 예외라곤 볼 수 없다. 여기에 오류의 원인을 알려준다면 더 좋은 예외로 볼 수 있을 것 같다. 아래의 코드는 메시지를 전달할 수 있도록 수정한 예제이다.

class TestException(Exception):
    def __init__(self, message=""):
        self.message = message
    def __str__(self):
        return f"TestException {self.message}"

def test():
    raise TestException("Some Error")

try:
    test()
except Exception as e:
    print(f'Exception: {e}')

이제 이 코드는 아래와 같은 메시지를 출력한다.

Exception: TestException Some Error

단순하게 만든 예제이지만 이 정도만 알아도 왠만한 오류 내용을 만들 수 있다.

예외 상황의 메시지를 그대로 출력하기

일반적으로 예외 처리가 완벽하지 않은 Python 스크립트에서 에러가 나면 스크립트가 죽으면서 Traceback이라 불리우는 메시지가 화면에 뿌려지는 것을 볼 수 있다.

Traceback (most recent call last):
  File "foo.py", line 13, in <module>
    bar()
  File "foo.py", line 10, in test
    raise QuxException("Foobar")
Exception: QuxException: Foobar

이런 메시지를 통해 에러의 종류와 발생 위치나 순서 등을 알 수 있어서 디버깅에 도움이 된다.

만약 작성 중인 스크립트에서 모든 예외를 핸들링 하기가 힘든 상황이라 예외 발생 시 스크립트를 죽이지 않으면서 그 내용을 눈으로 봐가며 디버깅해야 하는 경우 위의 Traceback 메시지를 예외 상황에서 표시할 수 있으면 유용할 때가 있다.

이럴 때는 traceback 모듈을 이용할 수 있다. 아래 코드는 do_what() 에서 예외가 발생하면 Traceback을 화면에 표시하는 예제다.

import traceback

try:
    do_what()
except:
    traceback.print_exc()

traceback 모듈의 print_exc() 함수는 실제로 콘솔에서 발생하던 오류와 거의 동일한 오류 메시지를 화면에 표시해준다. 따라서 디버깅에 유리하게 써먹을 수 있다.

만약 이런 Traceback을 로그 등으로 남기기 위해 별도의 문자열로 얻고 싶다면 format_exc() 함수를 활용할 수도 있다.

import traceback

try:
    do_what()
except:
    stacktrace = traceback.format_exc()
    log(stacktrace)

이렇게 하면 대부분의 Traceback을 로그로도 그대로 볼 수 있게 된다. 물론 스크립트는 당연히 죽지도 않고 말이다.