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


139

모듈을 작성 중이며 발생할 수있는 예외 (예 : FooError모든 foo모듈의 특정 예외에 대한 추상 클래스 에서 상속)에 대한 통합 예외 계층 구조를 원합니다 . 이를 통해 모듈 사용자는 이러한 특정 예외를 포착하고 필요한 경우 개별 예외를 처리 할 수 ​​있습니다. 그러나 모듈에서 발생하는 많은 예외는 다른 예외로 인해 발생합니다. 예를 들어 파일의 OSError로 인해 일부 작업이 실패합니다.

필요한 것은 다른 유형과 메시지 를 갖도록 포착 된 예외"랩핑"하여 예외를 포착 하는 모든 것에 의해 전파 계층 구조에 대한 정보를 추가로 사용할 수 있도록하는 것입니다. 그러나 기존 유형, 메시지 및 스택 추적을 잃고 싶지 않습니다. 그것은 문제를 디버깅하려는 누군가에게 유용한 정보입니다. 최상위 예외 처리기는 전파 스택을 더 높이기 전에 예외를 장식하려고하는데 최상위 처리기가 너무 늦기 때문에 좋지 않습니다.

이것은 foo기존 유형 (예 :)에서 모듈 의 특정 예외 유형을 파생시켜 부분적으로 해결 class FooPermissionError(OSError, FooError)되지만 기존 예외 인스턴스를 새로운 유형으로 랩핑하거나 메시지를 수정하기가 쉽지 않습니다.

Python의 PEP 3134 “Exception Chaining and Embedded Tracebacks”에서는 Python 3.0에서 예외 개체를“chaining”하기 위해 수용된 변경 사항에 대해 논의하여 기존 예외를 처리하는 동안 새로운 예외가 발생했음을 나타냅니다.

내가하려고하는 것은 관련이 있습니다. 이전 파이썬 버전에서도 작동해야하며 체인 연결이 아니라 다형성에만 필요합니다. 이것을하는 올바른 방법은 무엇입니까?


예외는 이미 완전히 다형성입니다. 모두 예외의 하위 클래스입니다. 무엇을하려고합니까? "다른 메시지"는 최상위 예외 처리기와 함께 아주 사소한 것입니다. 왜 수업을 바꾸고 있습니까?
S.Lott

질문에 설명 된 바와 같이 (현재 귀하의 의견에 감사드립니다) : 내가 잡은 예외를 꾸미려고 노력하여 더 많은 정보로 더 전파 할 수는 있지만 더 이상 잃지 않습니다. 최상위 처리기가 너무 늦습니다.
bignose

Python 2.x에서 원하는 것을 할 수있는 CausedException 클래스 를 살펴보십시오 . 또한 Python 3에서는 예외의 원인으로 둘 이상의 원래 예외를 제공하려는 경우에 사용할 수 있습니다. 아마도 그것은 당신의 요구에 맞을 것입니다.
Alfe


python-2의 경우 @DevinJeanpierre와 비슷한 작업을 수행하지만 새 문자열 메시지를 추가하고 있습니다 except Exception as e.-> raise type(e), type(e)(e.message + custom_message), sys.exc_info()[2]-> 이 솔루션은 다른 SO 질문에서 온 것 입니다. 이것은 예쁘지 않지만 기능적입니다.
Trevor Boyd Smith

답변:


197

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

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

따라서 포착 된 예외는 새 예외의 일부가되고 ( "원인") 새 예외를 포착하는 모든 코드에서 사용할 수 있습니다.

이 기능을 사용하면 __cause__속성이 설정됩니다. 내장 예외 처리기는 또한 예외의 "원인"및 "문맥" 을 역 추적과 함께 보고하는 방법을 알고 있습니다 .


Python 2 에서는 이 유스 케이스에 좋은 대답이없는 것으로 보입니다 ( Ian BickingNed Batchelder에 설명되어 있음 ). 버머.


4
Ian Bicking이 내 솔루션을 설명하지 않습니까? 나는 그런 엄청난 대답을 한 것을 후회하지만, 이것이 받아 들여지는 것은 이상합니다.
Devin Jeanpierre

1
@bignose 당신은 옳은 것뿐만 아니라 "frobnicate"의 사용에 대한 나의 요점을 얻었습니다 :)
David M.

5
예외 체인은 실제로 기본 동작이며, 실제로는 작업이 필요한 첫 번째 예외를 억제하면서 문제와 반대입니다. PEP 409 python.org/dev/peps/pep-0409
Chris_Rands

1
파이썬 2에서 어떻게 이것을 달성 하시겠습니까?
selotape

1
그것은 잘 작동하는 것 같습니다 (python 2.7)try: return 2 / 0 except ZeroDivisionError as e: raise ValueError(e)
alex

37

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가 필요하지 않습니다. 나는 그것을하지 않는 것이 좋습니다.


Devin, 거기에 역 추적에 대한 참조를 저장합니다. 해당 참조를 명시 적으로 삭제해서는 안됩니까?
Arafangion

2
나는 아무것도 저장하지 않았고, 아마도 범위를 벗어난 지역 변수로 트레이스 백을 남겼습니다. 예, 그렇지 않을 수도 있지만 함수가 아닌 전역 범위에서 이와 같은 예외를 발생 시키면 더 큰 문제가 있습니다. 귀하의 불만이 전 세계적으로 실행될 수 있다고 생각되는 경우 적절한 해결책은 설명해야하고 99 % 사용과 관련이없는 관련 보일러 플레이트를 추가하는 것이 아니라 솔루션이 다시 작성되지 않도록하는 것입니다 마치 지금처럼 다른 것이없는 것처럼 보이게하는 데 필요합니다.
Devin Jeanpierre

4
Arafangion은 @Devin에 대한 Python 문서sys.exc_info() 의 경고를 참조 할 수 있습니다 . "예외를 처리하는 함수에서 지역 변수에 역 추적 반환 값을 할당하면 순환 참조가 발생합니다." 그러나 다음 참고 사항에 따르면 Python 2.2 이후로주기를 정리할 수 있지만 피하는 것이 더 효율적입니다.
Don Kirkby

5
두 가지 깨달은 pythonistas에서 파이썬에서 예외를 다시 발생시키는 다른 방법에 대한 자세한 내용 : Ian BickingNed Batchelder
Rodrigue

11

포착 한 예외 를 확장하는 고유 한 예외 유형을 작성할 수 있습니다 .

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

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

그러나 대부분의 경우 예외를 포착하고 처리 raise하며 원래 예외 (및 역 추적 보존) 또는을 사용하는 것이 더 간단하다고 생각합니다 raise NewException(). 코드를 호출하고 있고 사용자 지정 예외 중 하나를 수신 한 경우 코드에서 이미 예외를 처리했을 것으로 예상됩니다. 따라서 나는 그것을 직접 접근 할 필요가 없다.

편집 : 나는 당신의 예외를 던지고 원래의 예외를 유지하는 방법에 대한이 분석 을 발견 했습니다 . 예쁜 해결책이 없습니다.


1
위에서 설명한 유스 케이스 는 예외 처리를 위한 것이 아닙니다 . 특히 처리 하지 않고 호출 스택에서 추가로 처리 할 수 ​​있도록 추가 정보 (추가 클래스 및 새 메시지)를 추가하는 것입니다.
bignose

2

또한 여러 번 오류 발생에 대한 "래핑"이 필요하다는 것을 알았습니다.

이것은 함수 범위에 포함되어 있으며 때로는 함수 내부의 일부 줄만 래핑합니다.

래퍼를 생성은 사용되는 decoratorcontext 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포장되어 발생합니다MyError


1
"래핑"이 원래 예외에 어떤 영향을 미치는지 설명하십시오. 코드의 목적은 무엇이며 어떤 행동을 가능하게합니까?
Alexis

@alexis-몇 가지 예를 추가했습니다. 도움이 되길 바랍니다
Aaron_ab

-2

귀하의 요구에 가장 확실한 솔루션은 다음과 같습니다.

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

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


1
예외 개체 를 잡아서 버립니다 . 그렇지 않으므로 질문의 요구를 충족시키지 못합니다. 질문은 기존 예외 를 유지 하고 스택에 계속 전파하기 위해 포함 된 모든 유용한 정보와 함께 동일한 예외를 허용하는 방법을 묻습니다 .
bignose
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.