[python] Exceptions, chained exceptions, traceback
A A

목차

    728x90

    Exception

    파이썬이 스크립트를 실행하고 처리할 수 없는 상황이면 다음과 같은 현상이 일어난다.

    • 프로그램을 중지한다.
    • Exception 이라는 특별한 종류의 데이터를 생성한다. 물론  Exception은 객체이다.

    이 두가지 활동 모두 예외 발생(raising an exception) 이라고 한다.

    Python은 코드에 어떤 작업이 필요한지 전혀 모를 때 항상 예외를 발생한다라고 할 수 있다.

    다음으로 어떤 일이 벌어질까 ?

    • 발생한 예외는 누군가 또는 무언가가 이를 알아차리고 처리할 것으로 기대한다.
    • 발생한 예외에 대한 처리가 이루어지지 않으면 프로그램은 강제종료되고 Python에서 콘솔로 오류메세지를 보낸다.
    • 그렇지 않을 경우, 예외가 적절히 처리되어 중단되었던 프로그램을 다시 시작할 수 있고, 실행을 계속할 수 있다.

    파이썬은 예외를 관찰하고, 식별하고, 효율적으로 처리할 수있는 효과적인 도구를 제공한다.

    모든 잠재적 예외에 명확한 이름이 부여되어 분류하고 적절하게 대응할 수 있기 때문에 가능하다.

    다음과 같은 예외 오류 메세지를 이미 본 적 있을 수 있다.

    IndexError : list index out of range

     

    필연적으로, 우리는 예외를 발생시키는 코드를 작성하게 될 것이다.

    그렇기 때문에 예외를 처리하는 방법을 아는 것이 매우 중요하다.

    파이썬에는 63가지의 내장 예외가 있고, 이러한 예외는 트리 형태의 계층 구조로 표현된다.

    가장 상위의 예외 클래스인 BaseException에서 상속된다.

    또 다른 의미로는, 사용자가 고유한 특정 예외 클래스를 생성할 수 있음을 의미한다.

    유일한 제약으로는 BaseException이나 다른 파생 예외 클래스를 서브클래싱 해야한다는 것이다.

    (* 내장 예외의 갯수는 Python 버전마다 다를 수 있다.)

    https://docs.python.org/3/library/exceptions.html

     

    Built-in Exceptions

    In Python, all exceptions must be instances of a class that derives from BaseException. In a try statement with an except clause that mentions a particular class, that clause also handles any excep...

    docs.python.org


    Exception handling

    코드에서 예외가 발생할 가능성이 의심되는 경우

    해당 try / except 코드 블록을 사용하여 "문제 발생 소지가 있는" 코드를 감싸야 한다.

    실제로 예외가 발생하더라도 실행이 종료되지는 않지만,

    해당 except 절 다음에 나오는 코드는 문제를 효과적으로 처리하려고 시도한다.

    try:
      print(int("a"))
    except ValueError:
      print("a는 정수형이 될 수 없다.")

    문자 a를 정수 값으로 변홚려고 할 때 예외가 발생한다.

    이 상황을 확장하여 보았을 때, int()의 경우 외부 데이터 소스를 받아 오는 경우 데이터 유형에 대해 완전히 신뢰할 수 없음으로

    "문제 발생 소지가 있다" 라고 보고 try/except 블록으로 감싸는 것이 좋다.


    Advanced exceptions : named attributes

    이제 예외를 조금 더 자세히 살펴보고 해당 객체를 사용할 수 있는 방법에 대해 알아보자.

    • ValueError
    try:
      print(int("a"))
    except ValueError as e:
      print("a는 정수형이 될 수 없다.")
      print(e.args)
    a는 정수형이 될 수 없다.
    ("invalid literal for int() with base 10: 'a'",)

    예외 절에서 as 를 이용하여 객체의 변수을 지칭할 수 있다.

    이 예에서는 e 가 예외 객체(인스턴스)를 대신한다. 해당 예외 인스턴스의 속성값을 통해 다양한 기능들을 볼 수 있다.

     

    • ImportError
    try :
      import abcdef
    
    except ImportError as e:
      print(e.args)
      print(e.name)
      print(e.path)
    ("No module named 'abcdef'",)
    abcdef
    None

    ImportError 같은 경우 코드를 보고 알 수 있듯, 모듈에 로드하는데 문제가 있을 때 발생한다.

    속성은 다음과 같다.

    ImportError_instance.name 가져오려고 시도한 모듈의 이름을 나타낸다.
    ImportError_instance.path 예외를 트리거한 파일의 경로 나타내며 없는 경우 None일 수 있다.

     

    • UnicodeError
    try:
      b'\x80'.decode("utf-8")
    except UnicodeError as e:
      print(e)
      print(e.encoding)
      print(e.reason)
      print(e.object)
      print(e.start)
      print(e.end)
    'utf-8' codec can't decode byte 0x80 in position 0: invalid start byte
    utf-8
    invalid start byte
    b'\x80'
    0
    1
    UnicodeError_instance.incoding 오류 발생시킨 인코딩의 이름
    UnicodeError_instance.reason 특정 코덱 오류를 설명하는 문자열
    UnicodeError_instance.object 코덱이 인코딩 또는 디코딩을 시도한 객체
    UnicodeError_instance.start 객체의 유효하지 않은 데이터의 첫 번째 인덱스
    UnicodeError_instance.end 객체의 유효하지 않은 데이터 다음의 인덱스

    chained exceptions

    파이썬 3에서는 예외를 효과적으로 처리하기 위해 "Exception chaining"이라는 매우 흥미로는 기능을 도입하였다.

     

    • 암시적 체이닝

    이미 예외를 처리하고 있는데 우연히 추가 예외가 발생하는 상황이 있을 수 있다.

    이전 예외에 대한 정보가 손실되지 않으면서, 오류가 발생한 코드 다음코드에서 해당 정보를 사용할 수 있어야한다.

    이는 암시적 체이닝의 예시이다.

    # 암시적 예외
    a_list = ["first_error", "second_error"]
    try:
      print(a_list[2])
    except IndexError as e:
      print(0/0)

    중간에 후속 추적을 결합하는 메세지가 포함된다.

    During handling of the above exception, another exception occurred:

    에러를 핸들링하는 와중에 다른 에러가 발생했다는 메모를 볼 수 있다.

    # 암시적 예외
    a_list = ["first_error", "second_error"]
    try:
      print(a_list[2])
    except Exception as e:
      try:
        print(1/0)
      except ZeroDivisionError as f:
        print("Outer exception e :",e)
        print("Inner exception f :",f)
        print("Outer exception referenced :",f.__context__)
        print("Is it the same object (f.__context__, e) :", f.__context__ is e)
    Outer exception e : list index out of range
    Inner exception f : division by zero
    Outer exception referenced : list index out of range
    Is it the same object (f.__context__, e) : True

    except Exception : 절은 매우 광범위한 블럭으로 일반적으로 처리되지 않은 모든 예외를 포착하는 최후의 수단으로서 사용해야

    한다. 어떤 종류의 예외가 발생할지 알 수 없기 때문에 프로그래머가 이후 코드를 바라보았을 때 의도를 파악하기 어렵기 때문이다.

    바깥쪽 Exception 객체 e는 ZeroDivisionError 객체인 f.__context__에 의해 참조된다.

     

    • 명시적 체이닝

    또 다른 경우로는 의도적으로 예외를 처리하고 다른 유형의 예외로 변환하려는 경우이다.

    이러한 상환은 한 코드의 통합 동작이 레거시 코드처럼 다른 코드와 유사하게 동작해야할 타당한 이유가 있을 때 일반적으로 발생한다.

    이 경우 이전 예외의 세부 정보를 유지하는 것이 좋다.

    이는 명시적 체이닝의 예시이다.

     

    예를 들면 이런 것이 있을 수 있다.

    로켓 발사 전 최종 확인 과정을 담당하는 코드를 생각해보면,

    확인 목록은 매우 길고, 각 확인 항목마다 다른 예외가 발생할 수 있다.

    하지만 매우 중요한 절차로, 모든 검사를 통과했는지 확인해야한다.

    만약 하나라도 불합격하면 로켓은 발사할 수 없다. 

    class RocketNotReadyError(Exception):
      pass
    
    def person_check(crew_list):
        try:
          print("captain's name :", crew_list[0])
          print("pilot's name :", crew_list[1])
          print("mechanic's name :", crew_list[2])
          print("navigator's name :", crew_list[3])
        except IndexError as e:
          raise RocketNotReadyError("Rocket Not Ready for takeoff") from e
    
    crew_list = ["John", "Mary", "Mike"]
    print("Final check procedure")
    
    person_check(crew_list)

    The above exception was the direct cause of the following exception:

    중간에 에러가 연결되어, 직접적인 원인에 대해 설명하고 있다.

    RocketNotReadyError의 예외 원인에 대해 파악하기 위해서, __cause__속성에 접근하면 다음과 같다.

    class RocketNotReadyError(Exception):
      pass
    
    def person_check(crew_list):
        try:
          print("captain's name :", crew_list[0])
          print("pilot's name :", crew_list[1])
          print("mechanic's name :", crew_list[2])
          print("navigator's name :", crew_list[3])
        except IndexError as e:
          raise RocketNotReadyError("Rocket Not Ready for takeoff") from e
    
    crew_list = ["John", "Mary", "Mike"]
    print("Final check procedure")
    
    try:
      person_check(crew_list)
    except RocketNotReadyError as f:
      print(f)
      print(f.__cause__)
    Final check procedure
    captain's name : John
    pilot's name : Mary
    mechanic's name : Mike
    Rocket Not Ready for takeoff
    list index out of range

    이번에는 안전하게 처리되었고, 해당 이유에 대한 print()도 함께 수행할 수 있었다.

     

     

    • __context__ : 암묵적으로 연결된 예외에 속성으로 내재됨.
    • __cause__ :명시적으로 연결된 예외에 속성으로 내재됨.

    이러한 속성은 프로그래머가 나중에 로깅 등의 처리를 위해 원래 예외 객체에 대한 참조를 편리하고 일관된 방식으로 유지하는 데 도움이 된다.

     

    traceback attribute

    각 예외 객체는 __traceback__ 속성을 가지고 있다.

    Python을 이용하면 각 예외 객체가 속성을 소유하므로 해당 속성(__traceback__)을 통해 추적정보를 알 수 있다.

    import traceback        #import
    
    class RocketNotReadyError(Exception):
      pass
    
    def person_check(crew_list):
        try:
          print("captain's name :", crew_list[0])
          print("pilot's name :", crew_list[1])
          print("mechanic's name :", crew_list[2])
          print("navigator's name :", crew_list[3])
        except IndexError as e:
          raise RocketNotReadyError("Rocket Not Ready for takeoff") from e
    
    crew_list = ["John", "Mary", "Mike"]
    print("Final check procedure")
    
    try:
      person_check(crew_list)
    except RocketNotReadyError as f:
      print(f.__traceback__)
      print(type(f.__traceback__))
      details = traceback.format_tb(f.__traceback__) #traceback.format_tb()
      for detail in details:
        print(detail)
    Final check procedure
    captain's name : John
    pilot's name : Mary
    mechanic's name : Mike
    <traceback object at 0x79d5a32261c0>
    <class 'traceback'>
    File "/tmp/ipython-input-3-1177193041.py", line 19, in <cell line: 0>
    person_check(crew_list)

    File "/tmp/ipython-input-3-1177193041.py", line 13, in person_check
    raise RocketNotReadyError("Rocket Not Ready for takeoff") from e

    이전 예외 체이닝의 결과들을 보면,

    RocketNotReadyError                  Traceback (most recent call last)

    와 같은 표기를 볼 수 있다.해당 출력을 통해 우리는 추적 유형 객체를 처리해야한다는 결론을 낼 수 있다.traceback 모듈에서 제공되는 format_tb()라는 메서드를 통해 추적을 설명하는 문자열 목록을 가져올 수 있다.

    경우에 따라 개발 프로젝트에서는 포괄적인 테스트 세션 이후에 기록된 추적 정보를 활용하여 통계를 수집하거나 버그 보고 프로세스를 자동화할 수도 있다.

     

    체이닝 예외와, 추적 속성에 대한 자세한 내용은 아래를 참고한다.

    https://peps.python.org/pep-3134/ 

     

    PEP 3134 – Exception Chaining and Embedded Tracebacks | peps.python.org

    This PEP proposes three standard attributes on exception instances: the __context__ attribute for implicitly chained exceptions, the __cause__ attribute for explicitly chained exceptions, and the __traceback__ attribute for the traceback. A new raise ....

    peps.python.org

     

    Copyright 2024. GRAVITY all rights reserved