파이썬 예외 처리 정리

Python // 2024년 07월 13일 작성

이 글은 Python의 예외(Exceptions) 처리의 가장 기초적인 부분을 정리한다. 참고로 문법은 버전 3.x 이후이며 2.x 버전에서도 문법은 비슷하지만 약간 느슨(?)할 수도 있다.

예외 처리 기본 구조

파이썬의 예외 처리의 가장 기본 키워드는 tryexcept다. 대충 아래와 같은 식으로 쓸 수 있다.

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

try 블록 내부에서 예외가 발생할지도 모를 코드를 배치하면 여기 안에서 예와가 발생할 경우 그 아래의 except 블록으로 플로우가 넘어가면서 예외를 처리할 수 있다. 여기서 오류를 처리하던지 프로그램을 종료시키던지 아니면 그저 로그만 남기고 넘어갈 수도 있다.

가장 기본적인 구조는 위와 같지만 요즘은 이렇게 쓰면 아마 누군가(?) 경고를 날려 줄 것이다. 정확히 말하자면 파이썬 커뮤니티에서는 명확한 예외의 이름을 명시하는 것을 추천하고 있다.

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

이렇게 하면 do_what()에서 발생되는 예외 중 ValueError 예외가 발생하면 except 블록으로 진입되어 처리할 수 있으나 이 외의 예외는 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로 처리할 수도 있다.

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 TestException as e:
    print(f'TestException: {e}')

TestException 클래스 정의에서 볼 수 있듯이 예외 클래스는 Exception을 상속 받은 클래스라 보면 된다.

이 예외 클래스에 메시지 기능을 간단히 추가하고자 한다면 문자열 변환 메소드를 추가해 주는 방법이 있다.

class TestException(Exception):
    def __str__(self):
        return "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}')

아마 기본적인 예외 클래스의 골격은 이런 형태면 될 것 같다.

예외 발생 시 트레이스백을 그대로 출력하기

예외가 발생할 경우 상세한 트레이스백(traceback) - 혹은 스택트레이스(stack trace) - 메시지를 콘솔에 표시해서 에러에 대처하는게 디버깅에 매우 유용하다. 이런 경우 traceback 모듈의 print_exc()함수를 이용할 수 있다. 예를 들자면 이런 식이다.

import traceback

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

이렇게 구현하면 try 블록 안에서 예외가 발생할 경우 상세한 트레이스백이 콘솔에 표시된다.

만약 이 트레이스백 등을 포함한 전체 예외 메시지를 로그로 남기기 위해 문자열로 받고 싶다면 format_exc() 함수를 사용할 수 있다.

import traceback

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

위 코드에서 log()라는 함수는 임의로 정의한 로그를 기록하는 함수다. 이런 식으로 상세한 예외 상황 메시지를 출력하거나 전달할 수 있다.