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

时间:2009-08-29 06:35:18

标签: python exception error-handling

我的背景是在C#中,我刚刚开始用Python编程。抛出异常时,我通常希望将其包装在另一个添加更多信息的异常中,同时仍然显示完整的堆栈跟踪。在C#中它很容易,但我如何在Python中完成它?

EG。在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 个答案:

答案 0 :(得分:191)

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

答案 1 :(得分:130)

Python 2

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

import sys
class MyException(Exception): pass

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

在捕获一个异常并重新引发另一个异常时始终执行此操作。

答案 2 :(得分:18)

Python 3具有链接异常的raise ... from clauseGlenn'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()的行,但异常消息现在包含命令行。

答案 3 :(得分:10)

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 :(得分:5)

我认为你不能在Python 2.x中做到这一点,但类似于这个功能的东西是Python 3的一部分。来自PEP 3134

  

在今天的Python实现中,异常由三个组成       parts:类型,值和回溯。 'sys'模块,       暴露三个并行变量exc_type中的当前异常,       exc_value和exc_traceback,sys.exc_info()函数返回一个       这三个部分的元组,'raise'语句有一个       接受这三个部分的三论证形式。操纵       异常通常需要并行传递这三个东西,       这可能是乏味且容易出错的。另外,'除外'       语句只能提供对值的访问,而不是跟踪。       将“追溯”属性添加到例外值可以实现全部       可以从一个地方访问的异常信息。

与C#比较:

  

C#中的异常包含一个只读的'InnerException'属性       可能指向另一个例外。它的文档[10]说明了这一点       “当一个异常X被抛出作为前一个的直接结果时       异常Y,X的InnerException属性应该包含一个       引用Y.“此属性不是由VM自动设置的;       相反,所有异常构造函数都采用可选的'innerException'       用于显式设置的参数。 “原因”属性已实现       与InnerException的目的相同,但是这个PEP提出了一个新形式       'raise'而不是扩展所有异常的构造函数。       C#还提供了一个直接跳转到的GetBaseException方法       InnerException链的末尾;这个PEP建议没有模拟。

另请注意,Java,Ruby和Perl 5也不支持此类事情。再次引用:

  

对于其他语言,Java和Ruby都丢弃原始语言       在'catch'/'rescue'中发生另一个异常时的异常       'finally'/'确保'条款。 Perl 5缺乏内置的结构化       异常处理。对于Perl 6,RFC编号88 [9]提出了一个例外       隐式保留数组中链式异常的机制       名为@@。

答案 5 :(得分:3)

您可以使用我的CausedException class来链接Python 2.x中的异常(甚至在Python 3中,如果您希望将多个捕获的异常作为新引发的异常的原因,它可能很有用)。也许它可以帮助你。

答案 6 :(得分: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

答案 7 :(得分:2)

假设:

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

答案 8 :(得分:2)

要获得Python 2和3的最大兼容性,可以在raise_from库中使用sixhttps://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)