【问题标题】:"Inner exception" (with traceback) in Python?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 年以来就没有出现过。

标签: python exception error-handling


【解决方案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 qq{OH 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,后者提供errornostrerror 属性。
      • 我将如何包装任意Error,例如ValueError,通过捕获Exception 进入RuntimeError?如果我重现您对这种情况的回答,堆栈跟踪就会丢失。
      • 我不确定你在问什么,@karl。你能在一个新问题中发布一个示例,然后从这里链接到它吗?
      • 我在stackoverflow.com/questions/23157766/… 编辑了我对OP 问题的副本,并直接考虑了您的回答。我们应该在那里讨论:)
      【解决方案9】:

      为了最大限度地实现 Python 2 和 3 之间的兼容性,您可以使用 six 库中的 raise_fromhttps://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)
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-12-03
        • 1970-01-01
        • 2015-06-06
        • 2012-04-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多