Python中的“内部异常”(带有回溯)?
Posted
技术标签:
【中文标题】Python中的“内部异常”(带有回溯)?【英文标题】:"Inner exception" (with traceback) in Python? 【发布时间】:2010-11-23 22:53:50 【问题描述】:我的背景是 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]
这包括消息和回溯,但它不显示在回溯中发生的异常。
【问题讨论】:
接受的答案已经过时了,也许你应该考虑接受另一个。 @AaronHall 不幸的是 OP 自 2015 年以来就没有出现过。 【参考方案1】:我认为您不能在 Python 2.x 中执行此操作,但类似于此功能的功能是 Python 3 的一部分。来自PEP 3134:
在今天的 Python 实现中,异常由三部分组成 部分:类型、值和回溯。 “系统”模块, 在三个并行变量 exc_type 中公开当前异常, exc_value 和 exc_traceback,sys.exc_info() 函数返回一个 这三个部分的元组,“raise”语句有一个 接受这三个部分的三参数形式。操纵 异常通常需要并行传递这三件事, 这可能很乏味且容易出错。此外,“除外” 语句只能提供对值的访问,而不是回溯。 将 'traceback' 属性添加到异常值会使所有 可以从一个地方访问的异常信息。
与 C# 的比较:
C# 中的异常包含一个只读的“InnerException”属性,该属性 可能指向另一个异常。它的文档 [10] 说 “当异常 X 作为先前的直接结果而引发时 异常 Y,X 的 InnerException 属性应包含 引用 Y。” 该属性不是由 VM 自动设置的; 相反,所有异常构造函数都采用可选的“innerException” 参数来明确设置它。 'cause' 属性满足 目的与 InnerException 相同,但此 PEP 提出了一种新形式 'raise' 而不是扩展所有异常的构造函数。 C# 还提供了一个 GetBaseException 方法,可以直接跳转到 InnerException 链的末端;这个 PEP 没有提出类似的建议。
还要注意,Java、Ruby 和 Perl 5 也不支持这种类型的东西。再次引用:
至于其他语言,Java 和 Ruby 都抛弃了原有的 'catch'/'rescue' 中发生另一个异常时的异常或 'finally'/'ensure' 子句。 Perl 5 缺乏内置的结构化 异常处理。对于 Perl 6,RFC 编号 88 [9] 提出了一个例外 在数组中隐式保留链式异常的机制 命名为@@。
【讨论】:
但是,当然,在 Perl5 中,您可以只说“confess qqOH NOES! $@”而不会丢失其他异常的堆栈跟踪。或者您可以实现自己的保留异常的类型。 这是过时的还是什么?你可以except Exception as err:
然后raise WhateverError('failed while processing ' + x) from err
而“innerException”就是from
的东西,对吧?【参考方案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
【讨论】:
【参考方案3】:在 Python 3.x 中:
raise Exception('Failed to process file ' + filePath).with_traceback(e.__traceback__)
或只是
except Exception:
raise MyException()
这将传播MyException
,但如果不处理则打印both异常。
在 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
【讨论】:
TypeError: raise: arg 3 must be a traceback or None 对不起,我犯了一个错误,不知何故我以为它也接受异常并自动获取它们的回溯属性。根据docs.python.org/3.1/reference/…,这应该是 e.__traceback__ @ilyan.: Python 2 没有e.__traceback__
属性!【参考方案4】:
Python 2
这很简单;将回溯作为第三个参数传递给 raise。
import sys
class MyException(Exception): pass
try:
raise TypeError("test")
except TypeError, e:
raise MyException(), None, sys.exc_info()[2]
在捕获一个异常并重新引发另一个异常时总是这样做。
【讨论】:
谢谢。这会保留回溯,但会丢失原始异常的错误消息。如何同时查看消息和回溯?raise MyException(str(e)), ...
等
Python 3 添加了raise E() from tb
和.with_traceback(...)
@GlennMaynard 这是一个很老的问题,但是raise
的中间参数是传递给异常的值(如果第一个参数是异常类而不是实例)。所以如果你想交换异常,而不是做raise MyException(str(e)), None, sys.exc_info()[2]
,最好使用这个:raise MyException, e.args, sys.exc_info()[2]
。
使用 future 包可以实现 Python2 和 3 兼容的方式:python-future.org/compatible_idioms.html#raising-exceptions E.g. from future.utils import raise_
和 raise_(ValueError, None, sys.exc_info()[2])
.【参考方案5】:
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")
【讨论】:
raise ... from ...
确实是在 Python 3 中执行此操作的正确方法。这需要更多的支持。
Nakedible
我认为这是因为不幸的是大多数人仍然没有使用 Python 3。
即使在 python 3 中使用 'from' 似乎也会发生这种情况
可以向后移植到 Python 2。希望有朝一日。
@ogrisel 您可以使用future
包来实现此目的:python-future.org/compatible_idioms.html#raising-exceptions E.g. from future.utils import raise_
和 raise_(ValueError, None, sys.exc_info()[2])
.【参考方案6】:
假设:
您需要一个适用于 Python 2 的解决方案(对于纯 Python 3,请参阅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
看起来关键部分是独立的简化的“raise”关键字。这将在 except 块中重新引发异常。
【讨论】:
这是 Python 2 & 3 兼容的解决方案!谢谢! 我认为这个想法是引发不同类型的异常。 这不是一连串的嵌套异常,只是重新引发一个异常 这是最好的 python 2 解决方案,如果您只需要丰富异常消息并拥有完整的堆栈跟踪! 使用 raise 和 raise from 有什么区别【参考方案7】:您可以使用我的CausedException class 在 Python 2.x 中链接异常(即使在 Python 3 中,如果您想将多个捕获的异常作为新引发的异常的原因,它也很有用)。也许它可以帮助你。
【讨论】:
【参考方案8】:Python 3 有 raise
... from
clause 来链接异常。 Glenn's answer 非常适合 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
statement 的那种风格将异常类型作为第一个表达式,将元组中的异常类构造函数参数作为第二个表达式,将回溯作为第三个表达式。如果您运行的版本早于 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()
的行,但异常消息现在包含命令行。
【讨论】:
ex.strerror
来自哪里?我在 Python 文档中找不到任何相关的命中。不应该是str(ex)
吗?
IOError
派生自EnvironmentError
、@hheimbuerger,后者提供errorno
和strerror
属性。
我将如何包装任意Error
,例如ValueError,通过捕获Exception
进入RuntimeError
?如果我重现您对这种情况的回答,堆栈跟踪就会丢失。
我不确定你在问什么,@karl。你能在一个新问题中发布一个示例,然后从这里链接到它吗?
我在***.com/questions/23157766/… 编辑了我对OP 问题的副本,并直接考虑了您的回答。我们应该在那里讨论:)【参考方案9】:
为了最大限度地实现 Python 2 和 3 之间的兼容性,您可以使用 six
库中的 raise_from
。 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)
【讨论】:
以上是关于Python中的“内部异常”(带有回溯)?的主要内容,如果未能解决你的问题,请参考以下文章
“自我”如何正确更新原始变量? N皇后问题中的递归/回溯(Python)