代码之家  ›  专栏  ›  技术社区  ›  EMP

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

  •  119
  • EMP  · 技术社区  · 15 年前

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

    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#中看到的一样。这在Python2.6中可能吗?看起来到目前为止,我能做的最好的事情(根据格伦·梅纳德的回答)是:

    try:
      ProcessFile(filePath)
    except Exception as e:
      raise Exception('Failed to process file' + filePath, e), None, sys.exc_info()[2]
    

    这包括消息和回溯,但它不显示在回溯中发生的异常。

    9 回复  |  直到 15 年前
        1
  •  284
  •   Jonathon Reinhart    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")
    
        2
  •  141
  •   Jonathon Reinhart    5 年前

    Python 2

    这很简单;将回溯作为要引发的第三个参数传递。

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

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

        3
  •  19
  •   Community Mr_and_Mrs_D    7 年前

    Python3具有 raise ... from clause 链接异常。 Glenn's answer 对于Python2.7来说非常好,但它只使用原始异常的回溯,并丢弃错误消息和其他细节。下面是Python2.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()

    任何异常类型

    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
  •  13
  •   ilya n.    15 年前

    在里面 :

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

    except Exception:
        raise MyException()
    

    哪一个会传播 MyException 但是打印 二者都 不处理的例外情况。

    在里面 Python2.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
  •  5
  •   ire_and_curses    15 年前

    我不认为在Python2.x中可以做到这一点,但类似于此功能的东西是Python3的一部分。从…起 PEP 3134

    部分:类型、值和回溯。“系统”模块, 在三个并行变量exc_type中公开当前异常, 这三个部分的元组,而“raise”语句有一个 接受这三部分的三个论证形式。操纵 这可能是乏味和容易出错的。此外,“除外” 语句只能提供对值的访问,而不能提供回溯。 回溯 '异常值的属性使所有 可从单个位置访问的异常信息。

    C#中的异常包含一个只读的“InnerException”属性,该属性 “当异常X是前一次事件的直接结果时 异常Y,X的InnerException属性应包含 相反,所有异常构造函数都采用可选的“innerException” 参数来显式设置它。这个 原因 与InnerException的目的相同,但本PEP提出了一种新形式 而不是扩展所有异常的构造函数。 InnerException链的末端;本政治公众人物不建议进行模拟。

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

    至于其他语言,Java和Ruby都抛弃了原来的语言 “捕获”/“救援”或中发生另一异常时的异常 “finally”/“sure”子句。Perl 5缺少内置的结构化 命名为@@。

        6
  •  5
  •   LexH    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
  •  3
  •   Andrew Barber Tejas Tank    12 年前

    你可以用我的 CausedException class

        8
  •  2
  •   brool    15 年前

    也许你可以抓住相关信息,把它传给别人?我的想法是:

    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
    
        9
  •  2
  •   HoldOffHunger Lux    4 年前

    • 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”关键字,它是独立的。这将在Exception块中重新引发异常。