본문 바로가기

Python Exceptions 간략 정리

기술적인 이야기/기타 개발 2020. 10. 9.
반응형

이 글은 파이썬(Python)의 예외 처리(Exception Handling)를 간단히 정리합니다. 개인적인 용도로 메모했던 내용을 글로 작성하는 거라 원하시는 내용이 없을 수도 있다는 점 양해 부탁드립니다. 문법은 3.x 기준이며 2.x에서는 약간의 수정이 필요할 수 있습니다만 웬만하면 3.x 쓰세요.

예외(Exceptions)가 뭐냐고요? 뭐 그냥 에러 혹은 오류라고 치죠. 성공이 아닌 나머지 모든 명확한 경우를 예외사항으로 취급할 수 있습니다.

예외 처리하기

파이썬의 예외 처리 방식은 try 블록 내에서만 가능합니다. 여기 안에 예외가 발생할지도 모를 코드를 실행시키고 여기서 예외가 발생하면 여기에 연결된 except 블록에서 해당 예외를 처리할 수 있습니다.

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

위에서 do_what() 함수가 예외를 발생시키면 except 안의 코드가 실행됩니다. 이 경우는 특별한 예외 케이스를 명시하지 않아서 아무 예외가 발생하면 except 내용이 실행됩니다.

만약 특정한 예외를 처리하고 싶다면 except 시 예외 클래스의 이름을 적어줄 수 있습니다.

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

ValueError라는 예외 사항만 처리하도록 코드를 수정하면 위와 같은 식입니다. 이외의 예외는 코드에 따라 무시되거나 혹은 프로그램을 오류로 종료시키도 스택 트레이스(Stack Traces)를 출력합니다.

예외의 정확한 정보를 알고 싶거나 예외 이름을 콘솔에 표시하고 싶다면 as 명령으로 해당 예외사항의 인스턴스를 받아와서 원하는 대로 처리할 수 있습니다.

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

위의 코드에서 ValueError 예외가 발생하면 해당 예외 클래스 인스턴스가 e라는 변수로 넘어오므로 이걸 출력하던가 다른 용도로 사용할 수 있게 됩니다. 만약 해당 예외 클래스에서 문자열 반환 메서드가 특정하게 구현되어 있다면 다양한 정보를 얻을 수도 있습니다.

한 코드에서 예외를 하나만 발생시키라는 법은 없습니다. 당연하게도 파이썬에서도 여러 예외를 검출하여 각각 처리할 수 있습니다

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

위의 예는 ValueError의 경우와 AttributeError의 경우를 별도로 처리하는 예제입니다.

만약 이렇게 두 가지 이상의 예외를 처리해야 하지만 처리해야 할 내용이 동일하다면 두 예외를 하나의 except 블록으로 묶을 수도 있습니다.

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

위의 예는 ValueErrorAttributeError 중 하나가 발생해도 동일한 코드가 실행되는 코드입니다.

여러 예외를 동시에 처리하는 경우에도 역시 as를 이용해 예외 클래스 인스턴스를 받아올 수 있습니다.

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

위의 예에서 지정된 예외가 발생하면 e 변수에 해당 예외 클래스 인스턴스를 받아올 수 있게 됩니다.

예외 발생시키기

파이썬에서 특정 예외를 발생시킬 때는 raise 명령을 이용합니다.

raise ValueError()

위의 코드는 ValueError 예외를 일으키는 예제입니다. 지금은 단순하게 탑 레벨의 코드로 보이지만, 예외 발생이 탑 레벨에 있는 경우는 흔치 않은 경우겠지요. 즉 함수나 메서드 구현 내부에서 사용하는 것이 맞습니다.

예제로 사용한 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}')

파이썬의 예외 클래스는 최소한 Exception 혹은 여기서 파생된 클래스에서 상속된 클래스여야 한다는 점만 지키면 자유롭게 작성이 가능합니다. 심지어 위처럼 클래스 이름만 있고 내용물이 없어도 일단 예외 클래스로써 동작은 합니다.

그런데 위 코드는 의도대로 동작하지 않는 문제가 하나 있습니다. except에서 예외를 잡아서 콘솔에 뭔가를 출력하려고 하는데 실제로 아래와 같이 화면에 예외 이름이 출력되지 않습니다.

Exception:

위의 예제의 의도는 해당 예외 클래스의 이름을 표시하려고 하는 것입니다. 따라서 이 문제를 해결하려면 해당 예외 클래스에서 문자열 변환 메서드인 __str__을 구현하면 됩니다.

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

위와 같이 구현하면 이제 예외 발생 시 print 문을 통해 정확하게 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

이런 식으로 예외 클래스는 디버깅을 위한 정보를 가지고 표현하는 등 여러 역할을 할 수도 있습니다.

이 정도면 파이썬으로 예외 처리를 하는 기본적인 수단은 전부 설명이 되는 것 같습니다.

팁: 예외 메시지를 그대로 출력하기

파이썬에서 예외를 try로 묶어서 따로 처리하지 않으면 예외 발생 시 프로그램이 강제로 종료되면서 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을 보고 싶다면 아래와 같은 식으로 처리하는 방법이 있습니다.

import traceback

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

traceback 모듈의 print_exc() 함수는 예외가 발생한 상황에서 가장 마지막의 Traceback 메시지를 콘솔에 표시시켜 주는 함수입니다. 따라서 위와 같이 아무 예외가 발생할 때 사용하면 원하는 대로 프로그램이 죽지 않으면서도 무슨 예외가 발생했는지 확인이 가능합니다.

만약 콘솔에 바로 표시하는 게 아니라 특정 로거(Logger)를 통해 Traceback 메시지를 남기고 싶다면 아래와 같이 format_exc() 함수를 이용하는 방법이 쓸 수 있습니다.

import traceback

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

format_exc() 함수는 Traceback 메시지를 문자열로 넘겨주는 함수입니다. 따라서 넘겨받은 문자열을 원하는 로거를 이용해 로그를 남기는 식으로 구현이 가능합니다.

728x90
반응형

댓글