source

기존 정보를 보존하면서 다른 유형 및 메시지로 예외 다시 발생

factcode 2023. 7. 13. 21:11
반응형

기존 정보를 보존하면서 다른 유형 및 메시지로 예외 다시 발생

는 모듈을 제기할 수 에)에 대한 통합 예외 계층을 가지고 싶습니다FooError▁▁class▁forfoo모듈의 특정 예외).이를 통해 모듈 사용자는 이러한 특정 예외를 파악하고 필요한 경우 이를 명확하게 처리할 수 있습니다.그러나 모듈에서 제기된 많은 예외는 파일의 OS 오류로 인해 일부 작업에서 실패하는 등의 다른 예외 때문에 발생합니다.

제가 필요로 하는 것은 탐지된 예외가 다른 유형과 메시지를 갖도록 "랩"하여 예외를 탐지한 것이 무엇이든 간에 정보를 전파 계층 위로 더 많이 사용할 수 있도록 하는 것입니다.그러나 기존 유형, 메시지 및 스택 추적을 손실하고 싶지 않습니다. 이 모든 정보는 문제를 디버그하려는 사용자에게 유용합니다.최상위 예외 처리기는 좋지 않습니다. 예외가 전파 스택 위로 더 올라가기 전에 장식하려고 하는데, 최상위 처리기가 너무 늦기 때문입니다.

이 문제는 부분적으로 내 모듈을 도출함으로써 해결됩니다.foo의 특정 예외 기유 특예외예정유형의형존예▁from((예:class FooPermissionError(OSError, FooError)하지만 이렇게 하면 기존 예외 인스턴스를 새 유형으로 묶거나 메시지를 수정하는 것이 더 쉬워지지 않습니다.

Python의 PEP 3134 "Exception Chaining and Embedded Tracebacks"는 기존 예외를 처리하는 동안 새로운 예외가 발생했음을 나타내기 위해 Python 3.0에서 "Chaining" 예외 개체에 대해 허용된 변경 사항에 대해 설명합니다.

제가 하려는 일은 다음과 같습니다.이전 버전의 파이썬에서도 작동해야 하며, 체인이 아닌 다형성에만 필요합니다.이를 위한 올바른 방법은 무엇입니까?

Python 3은 예외 체인을 도입했습니다(PEP 3134에 설명됨).이를 통해 예외를 제기할 때 기존 예외를 "원인"으로 인용할 수 있습니다.

try:
    frobnicate()
except KeyError as exc:
    raise ValueError("Bad grape") from exc

예외 (탐지된예외(예외▁()exc("원인") 새로운 예외인 ValueError의 일부가 됩니다."원인"은 새로운 예외를 포착하는 모든 코드에 적용됩니다.

으로써, 이기을사면하용능,,__cause__속성이 설정되었습니다.내장된 예외 처리기도 예외의 "원인" "상황"을 추적백과 함께 보고하는 방법을 알고 있습니다.


Python 2에서 이 사용 사례는 좋은 답변이 없는 것으로 보입니다(Ian BickingNed Batchelder의해 설명됨).아쉽습니다.

sys.exc_info()를 사용하여 추적을 되돌리고 PEP에서 언급한 대로 해당 추적을 사용하여 새 예외를 발생시킬 수 있습니다.이전 유형과 메시지를 보존하려면 예외에 대해 보존할 수 있지만, 예외에 해당하는 항목이 검색되는 경우에만 유용합니다.

예를들면

import sys

def failure():
    try: 1/0
    except ZeroDivisionError, e:
        type, value, traceback = sys.exc_info()
        raise ValueError, ("You did something wrong!", type, value), traceback

물론, 이것은 정말로 그렇게 유용하지 않습니다.만약 그렇다면, 우리는 그 PEP가 필요하지 않을 것입니다.저는 그것을 하는 것을 추천하지 않습니다.

탐지된 예외를 확장하는 고유한 예외 유형을 만들 수 있습니다.

class NewException(CaughtException):
    def __init__(self, caught):
        self.caught = caught

try:
    ...
except CaughtException as e:
    ...
    raise NewException(e)

하고 둘 중 하나를 선택하는 이 더 것 .raise 예외 트레이스백 원래및(예추보존을외또) 는raise NewException()코드를 호출할 때 사용자 지정 예외 중 하나를 받았다면 코드가 이미 발생한 예외를 처리했을 것입니다.따라서 제가 직접 액세스할 필요가 없습니다.

편집: 저는 당신 자신의 예외를 던지고 원래의 예외를 유지하는 방법에 대한 분석을 찾았습니다.좋은 해결책이 없습니다.

또한 오류가 발생할 경우 "포장"이 필요한 경우가 많습니다.

이것은 함수 범위에 모두 포함되며 때때로 함수 내부에 일부 선만 래핑합니다.

▁a▁to에 .decorator그리고.context manager:


실행

import inspect
from contextlib import contextmanager, ContextDecorator
import functools    

class wrap_exceptions(ContextDecorator):
    def __init__(self, wrapper_exc, *wrapped_exc):
        self.wrapper_exc = wrapper_exc
        self.wrapped_exc = wrapped_exc

    def __enter__(self):
        pass

    def __exit__(self, exc_type, exc_val, exc_tb):
        if not exc_type:
            return
        try:
            raise exc_val
        except self.wrapped_exc:
            raise self.wrapper_exc from exc_val

    def __gen_wrapper(self, f, *args, **kwargs):
        with self:
            for res in f(*args, **kwargs):
                yield res

    def __call__(self, f):
        @functools.wraps(f)
        def wrapper(*args, **kw):
            with self:
                if inspect.isgeneratorfunction(f):
                    return self.__gen_wrapper(f, *args, **kw)
                else:
                    return f(*args, **kw)
        return wrapper

사용 예

장식가

@wrap_exceptions(MyError, IndexError)
def do():
   pass

을 부를 때.do 방법, 마지세요하.IndexError, 친구야.MyError

try:
   do()
except MyError as my_err:
   pass # handle error 

컨텍스트 관리자

def do2():
   print('do2')
   with wrap_exceptions(MyError, IndexError):
       do()

東京의 do2에서, 서에context manager,한다면IndexError되고 올려질 입니다, 그은포장들어어것릴입올다니면지되들려것어올▁be▁is것다입니들▁it릴▁will.MyError

이것은 접선적인 것이지만, 제 라이브러리에 대해 일관된 오류 메시지를 작성할 때, 저는 우리가 오류 메시지가 스며들면서 우리 자신의 오류 메시지를 포장하고 있다는 것을 발견했습니다.Python 3.11이 추가 정보로 기존 오류를 보강하는 add_note 기능을 제공하는 것을 보고 기뻤습니다. 이 기능도 유용할 수 있습니다.

Python 3.11의 경우 add_note()

add_note() 메서드가 BaseException에 추가됩니다.예외가 발생할 때 사용할 수 없는 컨텍스트 정보로 예외를 강화하는 데 사용할 수 있습니다.추가된 노트는 기본 추적에 나타납니다.

그래서 우리는 지금 이 패턴을 따르고 있습니다.

try:
   some_risky_business()
except MyCustomException as ce:
  ce.add_note(f"Here is some more critical context!")
  raise se
except Exception as e:
  raise MyCustomException("Wow, didn't expect this error.") from e

@bignose의 답변에서 설명한 것처럼 예외를 "변환"하고 맥락이나 원인을 피하려면 몇 가지 까다로운 작업을 수행해야 합니다(아래의 Python 3).

import sys

new_ex = None

try:
    something_that_raises_ValueError()
except ValueError:
    _, _, tb = sys.exc_info()
    new_ex = TypeError('This is really how I want to report this')

if new_ex is not None:
    raise new_ex.with_traceback(tb)

트레이스백을 전달하면 문제가 발생한 곳을 가리킵니다.raise진술.

이것은 아마도 더 재사용할 수 있는 컨텍스트로 바뀔 수 있습니다.

메시지만 변경하려는 경우 다음을 조작할 수 있습니다.args이를 위한 두 가지 기능이 있습니다.

def append_message(e_: Exception, msg: str):
    """
    Appends `msg` to the message text of the exception `e`.

    Parameters
    ----------
    e_: Exception
        An exception instance whose `args` will be modified to include `msg`.

    msg: str
        The string to append to the message.
    """
    if e_.args:
        # Exception was raised with arguments
        e_.args = (str(e_.args[0]) + msg,) + e_.args[1:]
    else:
        e_.args = (msg,)


def replace_message(e_: Exception, msg: str):
    """
    Replaces the exception message with `msg`.

    Parameters
    ----------
    e_: Exception
        An exception instance whose `args` will be modified to be `msg`.

    msg: str
        The string to replace the exception message with.
    """
    if e_.args:
        e_.args = (msg,) + e_.args[1:]
    else:
        e_.args = (msg,)

고객의 요구사항에 가장 직접적인 해결책은 다음과 같습니다.

try:
     upload(file_id)
except Exception as upload_error:
     error_msg = "Your upload failed! File: " + file_id
     raise RuntimeError(error_msg, upload_error)

이러한 방식으로 나중에 메시지와 업로드 기능에 의해 발생한 특정 오류를 인쇄할 수 있습니다.

언급URL : https://stackoverflow.com/questions/696047/re-raise-exception-with-a-different-type-and-message-preserving-existing-inform

반응형