본문 바로가기

Programming Language/Python

[Python] Exception Chaining (raise ... from ...)

반응형

python 개발을 하면서 예외처리를 하다가 raise 와 from 이 같이 쓰이는 문법을 봤다. 어떤 용도로 쓰이는지 어떻게 동작하는지 궁금해서 관련한 내용들을 찾아보고 예제도 작성해보았다.

1. Exception Chaining

except 문 영역에서 처리되지 않는 예외가 발생하게되면, 처리중인 예외에 해당 내용이 에러 메시지에 추가되어 함께 처리되게 된다. 두 예외의 에러 메시지는 'During handling of the above exception, another exception occurred:' 라는 메시지로 연결되어 함께 출력되게 된다. 이 상황에서 raise 문에 from 을 함께 쓴다면 별개의 두 에러가 발생한 것이 아닌 에러의 인과관계를 연결하여 에러메시지를 처리할 수 있게 된다.

자세한 내용은 아래의 예제와 함께 확인해보자.

2. raise - from 예제

import traceback

def raise_without_from():
    try:
        raise_exception()
    except Exception as err:
        raise RuntimeError("in raise_without_from function. err={}".format(err))

def raise_with_from():
    try:
        raise_exception()
    except Exception as err:
        raise RuntimeError("in raise_with_from function. err={}".format(err)) from err

def raise_with_from_none():
    try:
        raise_exception()
    except Exception as err:
        raise RuntimeError("in raise_with_from_none function. err={}".format(err)) from None

def raise_exception():
    raise RuntimeError("origin exception occur")

if __name__ == "__main__":
    try:
        raise_without_from()
    except Exception as err:
        traceback.print_exc()

    print("-----------------------------")

    try:
        raise_with_from()
    except Exception as err:
        traceback.print_exc()

    print("-----------------------------")

    try:
        raise_with_from_none()
    except Exception as err:
        traceback.print_exc()

 

위 예제는 raise 문에서 from 을 어떻게 사용하는지에 따라 3가지 방식으로 예외를 처리한 코드이다. 각각의 방식에 따라 raise_without_from(), raise_with_from(), raise_with_from_none() 으로 함수를 구현했다. 각 함수를 호출했을 때 어떤 에러 메시지가 발생하는지 확인해보면서 raise - from 의 사용법을 알아보자.

- raise without from

Traceback (most recent call last):
    File "main.py", line 21, in raise_without_from
        raise_exception()
    File "main.py", line 26, in raise_exception
        raise RuntimeError("origin exception occur")
RuntimeError: origin exception occur

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
    File "main.py", line 31, in <module>
        raise_without_from()
    File "main.py", line 23, in raise_without_from
        raise RuntimeError("in raise_without_from function. err={}".format(err))
RuntimeError: in raise_without_from function. err=origin exception occur


일반적인 except 문으로 from 을 사용하지 않은 상황이다. 'raise_exception()' 함수에서 발생한 'RuntimeError''raise_without_from()' 함수에서 발생한 'RuntimeError''During handling of the above exception, another exception occurred: ~' 로 연결되어 출력되는 것을 확인할 수 있다.

raise_exception() 함수에서 발생항 예외에 의해서 raise_without_from() 함수의 에러가 발생했지만, 로그상으로는 마치 두가지 별개의 예외가 발생한 것처럼 보인다.

- raise with from

Traceback (most recent call last):
    File "main.py", line 15, in raise_with_from
        raise_exception()
    File "main.py", line 26, in raise_exception
        raise RuntimeError("origin exception occur")
RuntimeError: origin exception occur

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

Traceback (most recent call last):
    File "main.py", line 55, in <module>
        raise_with_from()
    File "main.py", line 17, in raise_with_from
        raise RuntimeError("in raise_with_from function. err={}".format(err)) from err
RuntimeError: in raise_with_from function. err=origin exception occur


raise 문에 from 을 사용한 경우에 발생하는 에러 메시지이다.

이전에 'During handling of the ...' 라는 메시지가 나왔던 것과는 달리 'The above exception was the direct cause of the following exception:' 라는 메시지로 두개의 에러 메시지가 연결되어있다.

이를 통해서 raise_exception() 함수에서 발생한 'Runtime Error: origin exception occur' 예외가 raise_with_from() 함수에서 발생한 'RuntimeError' 의 직접적인 원인이라는 것을 쉽게 알 수 있다.

- raise with from None

Traceback (most recent call last):
    File "main.py", line 93, in <module>
        raise_with_from_none()
    File "main.py", line 29, in raise_with_from_none
        raise RuntimeError("in raise_without_from function. err={}".format(err)) from None
RuntimeError: in raise_without_from function. err=origin exception occur


from 절에는 Exception 객체나 None 값이 들어갈 수 있다. from 절에 None 값을 사용되면 exception chaining 이 비활성화 되어 위의 메시지와 같이 나중에 발생하는 예외의 메시지만 전달되는 것을 알 수 있다.

3. Exception context

그렇다면 Exception 내부는 어떻게 구성되어서 예외간의 관계를 구성하는 것일까?

 

exception 객체는 해당 예외가 어디서 발생했는지에 대한 정보를 제공하는 3가지 속성이 있다.

 

BaseException.__context__: BaseException | None 암시적으로 연결된 exception 객체
BaseException.__cause__: BaseException | None 명시적으로 해당 예외의 원인으로 연결된 exception 객체
BaseException.__suppress_context__: bool 예외 발생 시, 예외 연결 정보를 표시할지 여부를 결정하는 속성

 

예외가 발생한 상황에서 새로운 예외를 발생시키는 경우, 새로 발생한 exception 객체의 __context__ 속성은 자동으로 처리되고 있는 exception 으로 설정된다.

 

처리된 예외는 except 절, finally 절 또는 with 문에 의해 처리되고 있는 예외를 의미한다.
이러한 exception context 에 대한 암시적인 설정은 raise ... from ... 문을 통한 명시적 원인 관계를 설정하여 보완할 수 있다.

 

raise new_exc from original_exc

 

from 절에는 execption 이나 None 값이 올 수 있다. 이 값은 raise 된 exception 의 __cause__ 로 설정된다.

 

__cause__ 설정은 __suppress_context__ 속성을 암시적으로 True 로 설정하는데, 'raise new_exc from None' 구문을 사용하면 이전 exception 을 대신하여 새로운 exception 이 화면에 출력되도록 할 수 있다. 이때 이전 exception 은 디버깅을 위해 __context__ 에 저장된다.

기본 traceback 출력 코드는 해당 exception 뿐만 아니라 연결된 exception 들에 대해서도 출력한다. __cause__ 에 명시적으로 연결된 예외가 존재하면 항상 출력된다. __context__ 에 암시적으로 연결된 예외는 __cause__ 가 None 이고 __suppress_context__ 가 false 일 때 출력된다.

 

어떤 경우에든 해당 예외는 연결된 예외들 다음에 출력된다. 그렇기 때문에 항상 traceback 의 마지막 줄에는 마지막으로 발생한 예외가 출력된다.

[Reference]
https://docs.python.org/3/tutorial/errors.html#exception-chaining
https://docs.python.org/3/library/exceptions.html#exception-context

 

8. Errors and Exceptions

Until now error messages haven’t been more than mentioned, but if you have tried out the examples you have probably seen some. There are (at least) two distinguishable kinds of errors: syntax error...

docs.python.org

 

 

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

 

반응형

'Programming Language > Python' 카테고리의 다른 글

[Python] Default Argument Value - mutable object  (2) 2023.12.02
[Python] GIL (Global Interpreter Lock)  (0) 2022.07.24
[Python] Awaitable  (0) 2022.04.19
[Python] Decorator  (0) 2022.04.01
[Python] module, package, library  (0) 2022.02.11