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


146

我的背景是C#,最近刚开始使用Python编程。当引发异常时,我通常希望将其包装在添加更多信息的另一个异常中,同时仍显示完整的堆栈跟踪。在C#中这很容易,但是如何在Python中做到呢?

例如。在C#中,我将执行以下操作:

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

在Python中,我可以执行类似的操作:

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

...但是这丢失了对内部异常的追溯!

编辑:我想同时看到异常消息和堆栈跟踪,并将两者关联起来。也就是说,我想在输出中看到异常X在这里发生,然后异常Y在这里发生-与我在C#中一样。这在Python 2.6中可行吗?到目前为止,看来我能做的最好的(根据Glenn Maynard的回答)是:

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

这既包括消息,也包括回溯,但是没有显示回溯中哪个异常发生。


3
接受的答案已经过时,也许您应该考虑接受另一个答案。
亚伦·霍尔

1
不幸的是,自2015
Antti Haapala,

Answers:


136

Python 2

这很简单; 将回溯作为第三个引发的参数。

import sys
class MyException(Exception): pass

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

捕获一个异常并重新引发另一个异常时,请始终执行此操作。


4
谢谢。这样可以保留回溯,但是会丢失原始异常的错误消息。如何查看消息和回溯?
EMP 2009年

6
raise MyException(str(e)), ...等等
格伦·梅纳德

23
Python 3中添加raise E() from tb.with_traceback(...)
迪马Tisnek

3
@GlennMaynard这是一个很老的问题,但是的中间参数raise是传递给异常的值(如果第一个参数是异常类而不是实例)。因此,如果您想交换异常而不是交换异常,raise MyException(str(e)), None, sys.exc_info()[2]则最好使用以下代码:raise MyException, e.args, sys.exc_info()[2]
bgusach 2015年

8
使用将来的软件包可以使用与Python2和3兼容的方式:python-future.org/compatible_idioms.html#raising-exceptions Eg from future.utils import raise_raise_(ValueError, None, sys.exc_info()[2])
jtpereyda

239

Python 3

在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

这将产生如下内容:

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

17
raise ... from ...确实是在Python 3中执行此操作的正确方法。这需要更多的支持。
Nakedible

Nakedible我认为这是因为很不幸,大多数人还没有使用Python 3
蒂姆Ludwinski

即使在python 3中使用“ from”,这似乎也会发生
Steve Vermeulen

可以反向移植到Python2。希望有一天。
Marcin Wojnarski

4
@ogrisel您可以使用该future软件包来实现此目标:python-future.org/compatible_idioms.html#raising-exceptions Eg from future.utils import raise_raise_(ValueError, None, sys.exc_info()[2])
jtpereyda

19

Python 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

这种raise声明风格将异常类型作为第一个表达式,将元组中的异常类构造函数参数作为第二个表达式,并将回溯作为第三个表达式。如果您运行的版本早于Python 2.2,请参阅中的警告sys.exc_info()

任何异常类型

如果您不知道代码可能必须捕获哪种异常,这是另一个更通用的示例。缺点是它将丢失异常类型,而只会引发RuntimeError。您必须导入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

修改讯息

如果异常类型允许您向其添加上下文,则这是另一种选择。您可以修改异常的消息,然后重新引发它。

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

生成以下堆栈跟踪:

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']

您可以看到它显示了check_output()被调用的行,但是异常消息现在包括命令行。


1
哪里ex.strerror来的?我在Python文档中找不到与此相关的任何匹配项。不是str(ex)吗?
Henrik Heimbuerger 2013年

1
IOError源自EnvironmentError@hheimbuerger,后者提供errornostrerror属性。
唐·柯比

如何通过Errorcatch 将任意值(例如ValueError)包装到中?如果我针对这种情况重现您的答案,则堆栈跟踪将丢失。RuntimeErrorException
Karl Richter

我不确定您在问什么,@ karl。您可以在新问题中发布样本,然后从此处链接到它吗?
Don Kirkby 2014年

我在stackoverflow.com/questions/23157766/…上编辑了OP的问题副本,并进行了澄清,直接考虑了您的答案。我们应该在那里讨论:)
Karl Richter 2014年

12

Python 3.x中

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

简单地

except Exception:
    raise MyException()

它将传播,MyException但如果不处理,则会打印两个异常。

Python 2.x中

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

您可以通过杀死该__context__属性来防止同时打印两个异常。在这里,我编写了一个上下文管理器,使用它来快速捕获和更改您的异常:(有关其工作原理的详细信息,请参见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

4
TypeError:引发:arg 3必须为回溯或无
Glenn Maynard

抱歉,我犯了一个错误,以某种方式我认为它也接受异常并自动获取其traceback属性。根据docs.python.org/3.1/reference/…,应为e .__ traceback__
ilya n。

1
@ilyan。:Python 2没有e.__traceback__属性!
Jan Hudec

5

我认为您无法在Python 2.x中执行此操作,但是与该功能相似的功能是Python 3的一部分。来自PEP 3134

在当今的Python实现中,异常由三部分组成:类型,值和回溯。'sys'模块以三个并行变量exc_type,exc_value和exc_traceback公开当前异常,sys.exc_info()函数返回这三个部分的元组,并且'raise'语句具有接受三个参数的形式这三个部分。处理异常通常需要并行传递这三件事,这可能是乏味且容易出错的。此外,“ except”语句只能提供对值的访问,而不能提供对追溯的访问。将' traceback '属性添加到异常值可以使所有异常信息都可以从一个位置访问。

与C#的比较:

C#中的异常包含一个只读的'InnerException'属性,该属性可能指向另一个异常。它的文档[10]说:“当由于先前的异常Y的直接结果而引发异常X时,X的InnerException属性应包含对Y的引用。” VM不会自动设置此属性。相反,所有异常构造函数都使用可选的“ innerException”参数来对其进行显式设置。在“ 事业 ”属性满足同样的目的的InnerException,但这个PEP提出的“提高”,而不是一直延伸异常的构造函数的新形式。C#还提供了一个GetBaseException方法,该方法直接跳转到InnerException链的末尾。

还要注意,Java,Ruby和Perl 5也不支持这种类型的东西。再次报价:

与其他语言一样,当“ catch” /“ rescue”或“ finally” /“ ensure”子句中发生另一个异常时,Java和Ruby都将丢弃原始异常。Perl 5缺少内置的结构化异常处理。对于Perl 6,RFC 88 [9]提出了一种异常机制,该机制隐式地将链式异常保留在名为@@的数组中。


但是,当然,在Perl5中,您只可以说“ confq qq {OH NOES!$ @}”,而不会丢失另一个异常的堆栈跟踪。或者,您可以实现自己的保留异常的类型。
jrockway

4

为了最大程度地兼容Python 2和3,可以raise_from在该six库中使用。 https://six.readthedocs.io/#six.raise_from。这是您的示例(为清晰起见,对其进行了稍微修改):

import six

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

3

您可以使用我的CausedException类在Python 2.x中链接异常(甚至在Python 3中,如果要将多个捕获的异常作为新引发的异常的原因,它也可能很有用)。也许可以帮到您。


2

也许您可以获取相关信息并将其传递出去?我在想类似的东西:

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

2

假设:

  • 您需要一个适用于Python 2的解决方案(有关纯Python 3,请参见raise ... from解决方案)
  • 只是想丰富错误消息,例如提供一些其他上下文
  • 需要完整的堆栈跟踪

您可以使用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

输出:

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

看起来关键是简化的“ raise”关键字,它独立存在。这将重新引发except块中的Exception。


这是Python 2和3兼容的解决方案!谢谢!
安迪·蔡斯

我认为这个主意是提出另一种例外类型。
蒂姆·卢德温斯基

2
这不是一堆嵌套的异常,只是引发了一个异常
Karl Richter

如果您只需要丰富异常消息并具有完整的堆栈跟踪,这是最好的python 2解决方案!
geekQ

使用引发和引发自
变量
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.