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

我的背景是 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]

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

53675 次浏览

我不认为您可以在 Python 2.x 中实现这一点,但类似的功能是 Python 3的一部分。来自 PEP 3134:

在今天的 Python 实现中,异常由三个异常组成 部件: 类型、值和回溯, 在三个并行变量 exc _ type 中公开当前异常, Exc _ value 和 exc _ traceback,sys.exc _ info ()函数返回一个 元组的这三个部分,并且’提高’语句有一个 接受这三个部分的三参数形式。操纵 异常通常需要并行地传递这三样东西, 除此之外,“除非” 语句只能提供对值的访问,而不能提供回溯。 将‘ 追踪’属性添加到异常值使所有 可从单个位置访问的异常信息。

与 C # 比较:

C # 中的异常包含一个只读的“ InnerException”属性,该属性 可能指向另一个例外。它的文档[10]说 ”当异常 X 作为前一个异常的直接结果引发时 例外 Y,X 的 InnerException 属性应该包含一个 该属性不是由 VM 自动设置的; 相反,所有的异常构造函数都有一个可选的“ innerException” 参数来显式设置它。‘ 原因’属性实现 与 InnerException 的目的相同,但是这个 PEP 提出了一种新的形式 而不是扩展所有异常的构造函数。 C # 还提供了一个 GetBaseException 方法,该方法直接跳转到 InnerException 链的末端; 这个 PEP 不提供模拟。

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

至于其他语言,Java 和 Ruby 都放弃了原始的 当另一个异常发生在“ catch”/“ save”或 “ finally”/“ sure”子句。 Perl 5缺乏内置的结构化 对于 Perl 6,RFC 编号88[9]提出了一个异常 隐式保留数组中的链式异常的机制 @@.

也许你可以拿到相关的信息然后传过去? 我在想这样的事情:

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

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

巨蟒2

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

import sys
class MyException(Exception): pass


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

在捕获一个异常并重新引发另一个异常时,总是这样做。

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

假设:

  • 您需要一个适用于 Python2的解决方案(对于纯 Python3,请参阅 raise ... from解决方案)
  • 只是想丰富错误消息,例如提供一些额外的上下文
  • 需要完整的堆栈跟踪

你可以使用文档 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

看起来关键的部分是简化的“加注”关键字,独立存在。这将重新引发“除外”块中的“异常”。

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

Python 3具有 ABC0... 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()的行,但是异常消息现在包含了命令行。

为了实现 Python2和 Python3之间的最大兼容性,可以在 six库中使用 raise_fromhttps://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)