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

2025-04-16 08:57:00
admin
原创
17
摘要:问题描述:我之前是 C# 程序员,最近才开始用 Python 编程。当一个异常抛出时,我通常想把它包装到另一个异常中,这样可以添加更多信息,同时仍然显示完整的堆栈跟踪。在 C# 中这很容易,但在 Python 中该怎么做呢?例如,在 C# 中我会做这样的事情:try { ProcessFile(fileP...

问题描述:

我之前是 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]

这包括消息和回溯,但没有显示回溯中发生了哪个异常。


解决方案 1:

Python 3

在python3中你可以执行以下操作:

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 2

这很简单;将回溯作为第三个参数传递给 raise。

import sys
class MyException(Exception): pass

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

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

解决方案 3:

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(),但是异常消息现在包含命令行。

解决方案 4:

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

解决方案 5:

我认为你不能在 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] 指出:“当异常 X 作为前一个异常 Y 的直接结果而引发时,X 的 InnerException 属性应包含对 Y 的引用。” 此属性并非由虚拟机自动设置;相反,所有异常构造函数都接受一个可选的“innerException”参数来显式设置它。“ cause ”属性实现与 InnerException 相同的目的,但本 PEP 提出了一种新的“raise”形式,而不是扩展所有异常的构造函数。C# 还提供了一个 GetBaseException 方法,可以直接跳转到 InnerException 链的末尾;本 PEP 未提出任何类似方法。

还要注意的是,Java、Ruby 和 Perl 5 也不支持这种类型。再次引用:

与其他语言一样,当“catch”/“rescue”或“finally”/“ensure”子句中出现另一个异常时,Java 和 Ruby 都会丢弃原始异常。Perl 5 缺少内置的结构化异常处理。对于 Perl 6,RFC 编号 88 [9] 提出了一种异常机制,该机制将链式异常隐式地保存在名为 @@ 的数组中。

解决方案 6:

为了最大程度地兼容 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)

解决方案 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:

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

解决方案 9:

假设:

  • 你需要一个适用于 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 块中重新引发异常。

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   2482  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1533  
  PLM(产品生命周期管理)项目对于企业优化产品研发流程、提升产品质量以及增强市场竞争力具有至关重要的意义。然而,在项目推进过程中,范围蔓延是一个常见且棘手的问题,它可能导致项目进度延迟、成本超支以及质量下降等一系列不良后果。因此,有效避免PLM项目范围蔓延成为项目成功的关键因素之一。以下将详细阐述三大管控策略,助力企业...
plm系统   0  
  PLM(产品生命周期管理)项目管理在企业产品研发与管理过程中扮演着至关重要的角色。随着市场竞争的加剧和产品复杂度的提升,PLM项目面临着诸多风险。准确量化风险优先级并采取有效措施应对,是确保项目成功的关键。五维评估矩阵作为一种有效的风险评估工具,能帮助项目管理者全面、系统地评估风险,为决策提供有力支持。五维评估矩阵概述...
免费plm软件   0  
  引言PLM(产品生命周期管理)开发流程对于企业产品的全生命周期管控至关重要。它涵盖了从产品概念设计到退役的各个阶段,直接影响着产品质量、开发周期以及企业的市场竞争力。在当今快速发展的科技环境下,客户对产品质量的要求日益提高,市场竞争也愈发激烈,这就使得优化PLM开发流程成为企业的必然选择。缺陷管理工具和六西格玛方法作为...
plm产品全生命周期管理   0  
热门文章
项目管理软件有哪些?
曾咪二维码

扫码咨询,免费领取项目管理大礼包!

云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用