development

파이썬에서 "내부 예외"(트레이스 백 포함)?

big-blog 2020. 7. 2. 07:19
반응형

파이썬에서 "내부 예외"(트레이스 백 포함)?


내 배경은 C #이며 최근에 Python으로 프로그래밍을 시작했습니다. 예외가 발생하면 일반적으로 전체 스택 추적을 표시하면서 더 많은 정보를 추가하는 다른 예외로 래핑하고 싶습니다. C #에서는 매우 쉽지만 파이썬에서는 어떻게합니까?

예 : C #에서는 다음과 같이 할 것입니다

try
{
  ProcessFile(filePath);
}
catch (Exception ex)
{
  throw new ApplicationException("Failed to process file " + filePath, ex);
}

파이썬에서는 비슷한 것을 할 수 있습니다.

try:
  ProcessFile(filePath)
except Exception as e:
  raise Exception('Failed to process file ' + filePath, e)

... 그러나 이것은 내부 예외의 역 추적을 잃습니다!

편집 : 예외 메시지와 스택 추적을 모두보고 싶습니다. 즉, 출력에서 ​​예외 X가 여기서 발생했다가 예외 Y가 발생했음을보고 싶습니다 .C #에서와 동일합니다. 파이썬 2.6에서 가능합니까? 내가 지금까지 할 수있는 최선의 모습 (Glenn Maynard의 답변을 바탕으로)은 ​​다음과 같습니다.

try:
  ProcessFile(filePath)
except Exception as e:
  raise Exception('Failed to process file' + filePath, e), None, sys.exc_info()[2]

여기에는 메시지와 역 추적이 모두 포함되지만 역 추적에서 발생한 예외는 표시되지 않습니다.


파이썬 2

간단 해; 세 번째 인수로 역 추적을 전달하십시오.

import sys
class MyException(Exception): pass

try:
    raise TypeError("test")
except TypeError, e:
    raise MyException(), None, sys.exc_info()[2]

한 예외를 포착하고 다른 예외를 다시 제기 할 때 항상이 작업을 수행하십시오.


파이썬 3

파이썬 3에서는 다음을 수행 할 수 있습니다.

try:
    raise MyExceptionToBeWrapped("I have twisted my ankle")

except MyExceptionToBeWrapped as e:

    raise MyWrapperException("I'm not in a good shape") from e

이것은 다음과 같은 것을 생성합니다 :

   Traceback (most recent call last):
   ...
   MyExceptionToBeWrapped: ("I have twisted my ankle")

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

   Traceback (most recent call last):
   ...
   MyWrapperException: ("I'm not in a good shape")

파이썬 3에는 예외를 연결 하는 raise... from절이 있습니다. Glenn의 답변 은 Python 2.7에 적합하지만 원래 예외의 역 추적 만 사용하고 오류 메시지 및 기타 세부 정보를 버립니다. 다음은 현재 범위의 컨텍스트 정보를 원래 예외의 오류 메시지에 추가하지만 다른 세부 정보는 그대로 유지하는 Python 2.7의 일부 예입니다.

알려진 예외 유형

try:
    sock_common = xmlrpclib.ServerProxy(rpc_url+'/common')
    self.user_id = sock_common.login(self.dbname, username, self.pwd)
except IOError:
    _, ex, traceback = sys.exc_info()
    message = "Connecting to '%s': %s." % (config['connection'],
                                           ex.strerror)
    raise IOError, (ex.errno, message), traceback

That flavour of raise statement takes the exception type as the first expression, the exception class constructor arguments in a tuple as the second expression, and the traceback as the third expression. If you're running earlier than Python 2.2, see the warnings on sys.exc_info().

Any Exception Type

Here's another example that's more general purpose if you don't know what kind of exceptions your code might have to catch. The downside is that it loses the exception type and just raises a RuntimeError. You have to import the traceback module.

except Exception:
    extype, ex, tb = sys.exc_info()
    formatted = traceback.format_exception_only(extype, ex)[-1]
    message = "Importing row %d, %s" % (rownum, formatted)
    raise RuntimeError, message, tb

Modify the Message

Here's another option if the exception type will let you add context to it. You can modify the exception's message and then reraise it.

import subprocess

try:
    final_args = ['lsx', '/home']
    s = subprocess.check_output(final_args)
except OSError as ex:
    ex.strerror += ' for command {}'.format(final_args)
    raise

That generates the following stack trace:

Traceback (most recent call last):
  File "/mnt/data/don/workspace/scratch/scratch.py", line 5, in <module>
    s = subprocess.check_output(final_args)
  File "/usr/lib/python2.7/subprocess.py", line 566, in check_output
    process = Popen(stdout=PIPE, *popenargs, **kwargs)
  File "/usr/lib/python2.7/subprocess.py", line 710, in __init__
    errread, errwrite)
  File "/usr/lib/python2.7/subprocess.py", line 1327, in _execute_child
    raise child_exception
OSError: [Errno 2] No such file or directory for command ['lsx', '/home']

You can see that it shows the line where check_output() was called, but the exception message now includes the command line.


In Python 3.x:

raise Exception('Failed to process file ' + filePath).with_traceback(e.__traceback__)

or simply

except Exception:
    raise MyException()

which will propagate MyException but print both exceptions if it will not be handled.

In Python 2.x:

raise Exception, 'Failed to process file ' + filePath, e

You can prevent printing both exceptions by killing the __context__ attribute. Here I write a context manager using that to catch and change your exception on the fly: (see http://docs.python.org/3.1/library/stdtypes.html for expanation of how they work)

try: # Wrap the whole program into the block that will kill __context__.

    class Catcher(Exception):
        '''This context manager reraises an exception under a different name.'''

        def __init__(self, name):
            super().__init__('Failed to process code in {!r}'.format(name))

        def __enter__(self):
            return self

        def __exit__(self, exc_type, exc_val, exc_tb):
            if exc_type is not None:
                self.__traceback__ = exc_tb
                raise self

    ...


    with Catcher('class definition'):
        class a:
            def spam(self):
                # not really pass, but you get the idea
                pass

            lut = [1,
                   3,
                   17,
                   [12,34],
                   5,
                   _spam]


        assert a().lut[-1] == a.spam

    ...


except Catcher as e:
    e.__context__ = None
    raise

I don't think you can do this in Python 2.x, but something similar to this functionality is part of Python 3. From PEP 3134:

In today's Python implementation, exceptions are composed of three parts: the type, the value, and the traceback. The 'sys' module, exposes the current exception in three parallel variables, exc_type, exc_value, and exc_traceback, the sys.exc_info() function returns a tuple of these three parts, and the 'raise' statement has a three-argument form accepting these three parts. Manipulating exceptions often requires passing these three things in parallel, which can be tedious and error-prone. Additionally, the 'except' statement can only provide access to the value, not the traceback. Adding the 'traceback' attribute to exception values makes all the exception information accessible from a single place.

Comparison to C#:

Exceptions in C# contain a read-only 'InnerException' property that may point to another exception. Its documentation [10] says that "When an exception X is thrown as a direct result of a previous exception Y, the InnerException property of X should contain a reference to Y." This property is not set by the VM automatically; rather, all exception constructors take an optional 'innerException' argument to set it explicitly. The 'cause' attribute fulfills the same purpose as InnerException, but this PEP proposes a new form of 'raise' rather than extending the constructors of all exceptions. C# also provides a GetBaseException method that jumps directly to the end of the InnerException chain; this PEP proposes no analog.

Note also that Java, Ruby and Perl 5 don't support this type of thing either. Quoting again:

As for other languages, Java and Ruby both discard the original exception when another exception occurs in a 'catch'/'rescue' or 'finally'/'ensure' clause. Perl 5 lacks built-in structured exception handling. For Perl 6, RFC number 88 [9] proposes an exception mechanism that implicitly retains chained exceptions in an array named @@.


For maximum compatibility between Python 2 and 3, you can use raise_from in the six library. https://six.readthedocs.io/#six.raise_from . Here is your example (slightly modified for clarity):

import six

try:
  ProcessFile(filePath)
except Exception as e:
  six.raise_from(IOError('Failed to process file ' + repr(filePath)), e)

You could use my CausedException class to chain exceptions in Python 2.x (and even in Python 3 it can be useful in case you want to give more than one caught exception as cause to a newly raised exception). Maybe it can help you.


Maybe you could grab the relevant information and pass it up? I'm thinking something like:

import traceback
import sys
import StringIO

class ApplicationError:
    def __init__(self, value, e):
        s = StringIO.StringIO()
        traceback.print_exc(file=s)
        self.value = (value, s.getvalue())

    def __str__(self):
        return repr(self.value)

try:
    try:
        a = 1/0
    except Exception, e:
        raise ApplicationError("Failed to process file", e)
except Exception, e:
    print e

Assuming:

  • you need a solution, which works for Python 2 (for pure Python 3 see raise ... from solution)
  • just want to enrich the error message, e.g. providing some additional context
  • need the full stack trace

you can use a simple solution from the the docs https://docs.python.org/3/tutorial/errors.html#raising-exceptions:

try:
    raise NameError('HiThere')
except NameError:
    print 'An exception flew by!' # print or log, provide details about context
    raise # reraise the original exception, keeping full stack trace

The output:

An exception flew by!
Traceback (most recent call last):
  File "<stdin>", line 2, in ?
NameError: HiThere

It looks like the key piece is the simplified 'raise' keyword that stands alone. That will re-raise the Exception in the except block.

참고URL : https://stackoverflow.com/questions/1350671/inner-exception-with-traceback-in-python

반응형