问题: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]
这既包括消息,也包括回溯,但是没有显示回溯中哪个异常发生。
回答 0
Python 2
这很简单; 将回溯作为第三个引发的参数。
import sys
class MyException(Exception): pass
try:
raise TypeError("test")
except TypeError, e:
raise MyException(), None, sys.exc_info()[2]
捕获一个异常并重新引发另一个异常时,请始终执行此操作。
回答 1
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")
回答 2
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()
被调用的行,但是异常消息现在包括命令行。
回答 3
在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
我认为您无法在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]提出了一种异常机制,该机制隐式地将链式异常保留在名为@@的数组中。
回答 5
为了最大程度地兼容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)
回答 6
您可以使用我的CausedException类在Python 2.x中链接异常(甚至在Python 3中,如果要将多个捕获的异常作为新引发的异常的原因,它也可能很有用)。也许可以帮到您。
回答 7
也许您可以获取相关信息并将其传递出去?我在想类似的东西:
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
回答 8
假设:
- 您需要一个适用于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。