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

我的背景是在C#中,我刚刚开始用Python编程。 当抛出一个exception时,我通常要把它包装在另一个exception中,以增加更多信息,同时仍然显示完整的堆栈跟踪。 在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) 

…但这失去了内部exception的追溯!

编辑:我想看到这两个exception消息和两个堆栈跟踪和相关的两个。 也就是说,我想在输出中看到exceptionX在这里出现,然后出现exceptionY – 和我在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] 

这包括消息和两个回溯,但是它不显示在回溯中发生了哪个exception。

这很简单; 把追踪作为第三个参数来提高。

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

当发现一个exception并重新提出exception时,总是这样做。

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

Python 3具有raisefrom子句到链式exception。 对于Python 2.7, Glenn的答案很好,但它只使用原始exception的回溯,并抛出错误信息和其他细节。 以下是Python 2.7中的一些示例,它将当前作用域的上下文信息添加到原始exception的错误消息中,但保留其他细节。

已知的例外types

 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语句的风格以exceptiontypes作为第一个expression式,exception类构造函数中的元组作为第二个expression式,而追溯作为第三个expression式。 如果您运行的是Python 2.2之前的版本,请参阅sys.exc_info()上的警告。

任何exceptiontypes

这是另一个更通用的例子,如果你不知道你的代码可能需要捕捉什么样的exception。 缺点是它失去了exceptiontypes,只是引发了一个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 

修改消息

如果exceptiontypes允许您添加上下文,则还有另一种select。 您可以修改exception的消息,然后重新评估它。

 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()的那一行,但是这个exception消息现在包含了命​​令行。

Python 3.x中

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

干脆

 except Exception: raise MyException() 

这将传播MyException但如果它不会被处理,则会打印两个exception。

Python 2.x中

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

您可以通过杀死__context__属性来防止打印这两个exception。 在这里,我编写了一个上下文pipe理器,用于捕获和更改您的exception:(请参阅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 

我不认为你可以在Python 2.x中做到这一点,但类似于这个function的东西是Python 3的一部分。从PEP 3134 :

在今天的Python实现中,exception由三部分组成:types,值和回溯。 'sys'模块以三个并行variablesexc_type,exc_value和exc_traceback显示当前exception,sys.exc_info()函数返回这三个部分的一个元组,而'raise'语句具有三个参数的forms,接受这三个部分。 处理exception往往需要同时通过这三件事情,这可能是乏味和容易出错的。 此外,“except”语句只能提供对值的访问,而不是回溯。 将“ traceback ”属性添加到exception值可以使所有exception信息从一个地方访问。

与C#比较:

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

还要注意,Java,Ruby和Perl 5也不支持这种types的东西。 再次引用:

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

您可以使用我的CausedException类在Python 2.x中链接exception(即使在Python 3中,如果您想要将多个捕获的exception作为新引发exception的原因,也可以使用它)。 也许它可以帮助你。

也许你可以抓住相关信息并把它传递出去? 我在想:

 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 2的解决scheme(对于纯Python 3,请参阅raise ... from solution)
  • 只是想丰富错误信息,例如提供一些额外的上下文
  • 需要完整的堆栈跟踪

您可以使用文档https://docs.python.org/3/tutorial/errors.html#raising-exceptions中的简单解决scheme:;

 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 

它看起来像关键的一块是简单的“提高”关键字是独立的。 这将在except块中重新提升Exception。