简体   繁体   English

Python 中的“内部异常”(带回溯)?

[英]“Inner exception” (with traceback) in Python?

My background is in C# and I've just recently started programming in Python.我的背景是 C#,我最近才开始用 Python 编程。 When an exception is thrown I typically want to wrap it in another exception that adds more information, while still showing the full stack trace.当抛出异常时,我通常希望将它包装在另一个添加更多信息的异常中,同时仍然显示完整的堆栈跟踪。 It's quite easy in C#, but how do I do it in Python?在 C# 中很容易,但我如何在 Python 中做到这一点?

Eg.例如。 in C# I would do something like this:在 C# 中,我会做这样的事情:

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

In Python I can do something similar:在 Python 中,我可以做类似的事情:

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

...but this loses the traceback of the inner exception! ...但这失去了内部异常的回溯!

Edit: I'd like to see both exception messages and both stack traces and correlate the two.编辑:我想同时查看异常消息和堆栈跟踪并将两者关联起来。 That is, I want to see in the output that exception X occurred here and then exception Y there - same as I would in C#.也就是说,我想在输出中看到异常 X 发生在这里,然后异常 Y 出现在那里——就像我在 C# 中所做的那样。 Is this possible in Python 2.6?这在 Python 2.6 中可能吗? Looks like the best I can do so far (based on Glenn Maynard's answer) is:看起来到目前为止我能做的最好的事情(基于 Glenn Maynard 的回答)是:

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

This includes both the messages and both the tracebacks, but it doesn't show which exception occurred where in the traceback.这包括消息和回溯,但它没有显示在回溯中发生了哪个异常。

Python 3蟒蛇 3

In python 3 you can do the following:在python 3中,您可以执行以下操作:

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

except MyExceptionToBeWrapped as e:

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

This will produce something like this:这将产生如下内容:

   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")

Python 2蟒蛇 2

It's simple;这很简单; pass the traceback as the third argument to raise.将回溯作为第三个参数传递给 raise。

import sys
class MyException(Exception): pass

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

Always do this when catching one exception and re-raising another.在捕获一个异常并重新引发另一个异常时总是这样做。

Python 3 has the raise ... from clause to chain exceptions. Python 3 有raise ... from子句到链异常。 Glenn's answer is great for Python 2.7, but it only uses the original exception's traceback and throws away the error message and other details. Glenn 的答案对 Python 2.7 来说很棒,但它只使用原始异常的回溯并丢弃错误消息和其他详细信息。 Here are some examples in Python 2.7 that add context information from the current scope into the original exception's error message, but keep other details intact.以下是 Python 2.7 中的一些示例,它们将当前作用域中的上下文信息添加到原始异常的错误消息中,但保持其他详细信息不变。

Known Exception Type已知异常类型

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.这种raise语句将异常类型作为第一个表达式,将元组中的异常类构造函数参数作为第二个表达式,将回溯作为第三个表达式。 If you're running earlier than Python 2.2, see the warnings on sys.exc_info() .如果您运行的版本早于 Python 2.2,请参阅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.缺点是它丢失了异常类型,只会引发 RuntimeError。 You have to import the traceback module.您必须导入traceback模块。

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.您可以看到它显示了调用check_output()的行,但异常消息现在包括命令行。

In Python 3.x :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.这将传播MyException但如果不会处理它,则会打印这两个异常。

In Python 2.x :Python 2.x 中

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

You can prevent printing both exceptions by killing the __context__ attribute.您可以通过终止__context__属性来防止打印这两个异常。 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)在这里,我编写了一个上下文管理器,使用它来动态捕获和更改您的异常:(有关它们如何工作的说明,请参见http://docs.python.org/3.1/library/stdtypes.html

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 :我不认为你可以在 Python 2.x 中做到这一点,但类似于这个功能的东西是 Python 3 的一部分。来自PEP 3134

In today's Python implementation, exceptions are composed of three parts: the type, the value, and the traceback.在今天的 Python 实现中,异常由三部分组成:类型、值和回溯。 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. 'sys' 模块在三个并行变量 exc_type、exc_value 和 exc_traceback 中公开当前异常,sys.exc_info() 函数返回这三个部分的元组,'raise' 语句具有接受的三参数形式这三个部分。 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.此外,'except' 语句只能提供对值的访问,而不能提供对回溯的访问。 Adding the ' traceback ' attribute to exception values makes all the exception information accessible from a single place.将“回溯”属性添加到异常值可以从一个地方访问所有异常信息。

Comparison to C#:与 C# 的比较:

Exceptions in C# contain a read-only 'InnerException' property that may point to another exception. C# 中的异常包含一个只读的“InnerException”属性,该属性可能指向另一个异常。 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."它的文档 [10] 说“当异常 X 作为前一个异常 Y 的直接结果被抛出时,X 的 InnerException 属性应该包含对 Y 的引用。” This property is not set by the VM automatically;此属性不是由 VM 自动设置的; rather, all exception constructors take an optional 'innerException' argument to set it explicitly.相反,所有异常构造函数都采用可选的“innerException”参数来显式设置它。 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. ' cause ' 属性实现与 InnerException 相同的目的,但是这个 PEP 提出了一种新的 'raise' 形式,而不是扩展所有异常的构造函数。 C# also provides a GetBaseException method that jumps directly to the end of the InnerException chain; C#还提供了一个直接跳转到InnerException链末端的GetBaseException方法; this PEP proposes no analog.此 PEP 不建议模拟。

Note also that Java, Ruby and Perl 5 don't support this type of thing either.还要注意 Java、Ruby 和 Perl 5 也不支持这种类型的东西。 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.对于其他语言,当“catch”/“rescue”或“finally”/“ensure”子句中出现另一个异常时,Java 和 Ruby 都会丢弃原始异常。 Perl 5 lacks built-in structured exception handling. Perl 5 缺乏内置的结构化异常处理。 For Perl 6, RFC number 88 [9] proposes an exception mechanism that implicitly retains chained exceptions in an array named @@.对于 Perl 6,RFC 编号 88 [9] 提出了一种异常机制,该机制在名为 @@ 的数组中隐式保留链式异常。

For maximum compatibility between Python 2 and 3, you can use raise_from in the six library.为了最大程度地兼容 Python 2 和 3,您可以在six库中使用raise_from https://six.readthedocs.io/#six.raise_from . 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).您可以使用我的CausedException 类来链接 Python 2.x 中的异常(即使在 Python 3 中,如果您想将多个捕获的异常作为新引发的异常的原因,它也很有用)。 Maybe it can help you.也许它可以帮助你。

Assuming:假设:

  • you need a solution, which works for Python 2 (for pure Python 3 see raise ... from solution)您需要一个适用于 Python 2 的解决方案(对于纯 Python 3,请参阅raise ... from solution)
  • just want to enrich the error message, eg providing some additional context只是想丰富错误信息,例如提供一些额外的上下文
  • need the full stack trace需要完整的堆栈跟踪

you can use a simple solution from the docs https://docs.python.org/3/tutorial/errors.html#raising-exceptions :您可以使用文档https://docs.python.org/3/tutorial/errors.html#rising-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.看起来关键部分是独立的简化的“raise”关键字。 That will re-raise the Exception in the except block.这将重新引发except块中的异常。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM