Python中的“内部异常”(及其回溯)是什么?

177 投票
9 回答
62573 浏览
提问于 2025-04-15 13:57

我之前主要用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]

这包括了两个消息和两个追踪信息,但没有显示哪个异常在追踪信息中发生在哪里。

9 个回答

21

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() 被调用的那一行,但异常消息现在包含了命令行的信息。

336

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

Python 2

这很简单;把错误追踪信息作为第三个参数传给 raise。

import sys
class MyException(Exception): pass

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

在捕获一个异常后再抛出另一个异常时,记得总是这样做。

撰写回答