【问题标题】:Adding information to an exception?向异常添加信息?
【发布时间】:2011-08-29 02:05:45
【问题描述】:

我想实现这样的目标:

def foo():
   try:
       raise IOError('Stuff ')
   except:
       raise

def bar(arg1):
    try:
       foo()
    except Exception as e:
       e.message = e.message + 'happens at %s' % arg1
       raise

bar('arg1')
Traceback...
  IOError('Stuff Happens at arg1')

但我得到的是:

Traceback..
  IOError('Stuff')

关于如何实现这一点的任何线索?如何在 Python 2 和 3 中都做到这一点?

【问题讨论】:

标签: python exception


【解决方案1】:

如果你来这里寻找 Python 3 的解决方案 the manual 说:

当引发一个新异常时(而不是使用一个裸露的raise 来重新引发当前正在处理的异常),隐式异常上下文可以通过使用fromraise 来补充一个显式原因:

raise new_exc from original_exc

例子:

try:
    return [permission() for permission in self.permission_classes]
except TypeError as e:
    raise TypeError("Make sure your view's 'permission_classes' are iterable. "
                    "If you use '()' to generate a set with a single element "
                    "make sure that there is a comma behind the one (element,).") from e

最后是这样的:

2017-09-06 16:50:14,797 [ERROR] django.request: Internal Server Error: /v1/sendEmail/
Traceback (most recent call last):
File "venv/lib/python3.4/site-packages/rest_framework/views.py", line 275, in get_permissions
    return [permission() for permission in self.permission_classes]
TypeError: 'type' object is not iterable 

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
    # Traceback removed...
TypeError: Make sure your view's Permission_classes are iterable. If 
     you use parens () to generate a set with a single element make 
     sure that there is a (comma,) behind the one element.

将一个完全不起眼的TypeError 变成一个很好的消息,其中包含解决方案的提示,而不会弄乱原始异常。

【讨论】:

  • 这是最好的解决方案,因为产生的异常指向最初的原因,请提供更多细节。
  • 有什么解决方案可以让我们添加一些消息但仍然不会引发新的异常?我的意思是扩展异常实例的消息。
  • Yaa~~ 它有效,但感觉像是不应该对我做的事情。消息存储在e.args 中,但这是一个元组,因此无法更改。所以首先将args复制到一个列表中,然后修改它,然后将其复制回一个元组:args = list(e.args)args[0] = 'bar'e.args = tuple(args)
  • @edcSam,我发现这适用于自定义异常:def __init__(self, message): super().__init__("some prefix: " + message)
【解决方案2】:

我会这样做,因此在 foo() 中更改其类型不需要在 bar() 中也进行更改。

def foo():
    try:
        raise IOError('Stuff')
    except:
        raise

def bar(arg1):
    try:
        foo()
    except Exception as e:
        raise type(e)(e.message + ' happens at %s' % arg1)

bar('arg1')

Traceback (most recent call last):
  File "test.py", line 13, in <module>
    bar('arg1')
  File "test.py", line 11, in bar
    raise type(e)(e.message + ' happens at %s' % arg1)
IOError: Stuff happens at arg1

更新 1

以下是保留原始回溯的轻微修改:

...
def bar(arg1):
    try:
        foo()
    except Exception as e:
        import sys
        raise type(e), type(e)(e.message +
                               ' happens at %s' % arg1), sys.exc_info()[2]

bar('arg1')

Traceback (most recent call last):
  File "test.py", line 16, in <module>
    bar('arg1')
  File "test.py", line 11, in bar
    foo()
  File "test.py", line 5, in foo
    raise IOError('Stuff')
IOError: Stuff happens at arg1

更新 2

对于 Python 3.x,我第一次更新中的代码在语法上不正确,加上 BaseException 上具有 message 属性的想法是 2012 年 5 月 16 日的 retracted in a change to PEP 352(我的第一次更新发布于 2012 年) -03-12)。所以目前,无论如何,在 Python 3.5.2 中,您需要按照这些方式做一些事情来保留回溯,而不是硬编码函数 bar() 中的异常类型。还要注意会有一行:

During handling of the above exception, another exception occurred:

在显示的回溯消息中。

# for Python 3.x
...
def bar(arg1):
    try:
        foo()
    except Exception as e:
        import sys
        raise type(e)(str(e) +
                      ' happens at %s' % arg1).with_traceback(sys.exc_info()[2])

bar('arg1')

更新 3

一位评论者询问是否有一种方法可以在 Python 2 和 3 中使用。虽然由于语法差异,答案可能看起来是“否”,但有一种解决方法通过在 six 附加模块中使用像 reraise() 这样的辅助函数。因此,如果您出于某种原因不想使用该库,下面是一个简化的独立版本。

还要注意,由于在 reraise() 函数中重新引发了异常,因此无论引发什么回溯,它都会出现,但最终结果是您想要的。

import sys

if sys.version_info.major < 3:  # Python 2?
    # Using exec avoids a SyntaxError in Python 3.
    exec("""def reraise(exc_type, exc_value, exc_traceback=None):
                raise exc_type, exc_value, exc_traceback""")
else:
    def reraise(exc_type, exc_value, exc_traceback=None):
        if exc_value is None:
            exc_value = exc_type()
        if exc_value.__traceback__ is not exc_traceback:
            raise exc_value.with_traceback(exc_traceback)
        raise exc_value

def foo():
    try:
        raise IOError('Stuff')
    except:
        raise

def bar(arg1):
    try:
       foo()
    except Exception as e:
        reraise(type(e), type(e)(str(e) +
                                 ' happens at %s' % arg1), sys.exc_info()[2])

bar('arg1')

【讨论】:

  • 这失去了回溯,有点违背了向现有异常添加信息的目的。此外,它不适用于带有 >1 个参数的 ctor 异常(类型是您无法从捕获异常的地方控制的东西)。
  • @Václav:防止丢失回溯相当容易——如我添加的更新所示。虽然这仍然不能处理所有可能的异常,但它确实适用于类似于 OP 问题中所示的情况。
  • 这不是完全正确的。如果 type(e) 覆盖 __str__,您可能会得到不希望的结果。另请注意,第二个参数传递给第一个参数给定的构造函数,这会产生有点荒谬的type(e)(type(e)(e.message)。第三,e.message is deprecated 支持 e.args[0]。
  • 那么,没有一种可移植的方式同时适用于 Python 2 和 3 吗?
  • @martineau 在 except 块中导入的目的是什么?这是为了节省内存,只在必要时导入吗?
【解决方案3】:

假设你不想或不能修改 foo(),你可以这样做:

try:
    raise IOError('stuff')
except Exception as e:
    if len(e.args) >= 1:
        e.args = (e.args[0] + ' happens',) + e.args[1:]
    raise

这确实是这里唯一解决 Python 3 问题的解决方案,没有丑陋且令人困惑的“在处理上述异常期间,发生了另一个异常”消息。

如果应该将重新提升的行添加到堆栈跟踪中,写 raise e 而不是 raise 就可以了。

【讨论】:

  • 但在这种情况下,如果 foo 中的异常发生变化,我也必须更改 bar。?
  • 如果你捕获了 Exception(上面编辑过),你可以捕获任何标准库异常(以及那些从 Exception 继承并调用 Exception.__init__ 的异常)。
  • 为了更加完整/合作,包括原始元组的其他部分:e.args = ('mynewstr' + e.args[0],) + e.args[1:]
  • @nmz787 这实际上是 Python 3 的最佳 解决方案。你的错误到底是什么?
  • @Dubslow 和 martineau 我将您的建议纳入编辑中。
【解决方案4】:

到目前为止,我不喜欢所有给出的答案。他们仍然太冗长恕我直言。在代码和消息输出中。

我想要的只是指向源异常的堆栈跟踪,中间没有异常内容,因此不会创建新异常,只需使用所有相关堆栈帧状态重新提升原始异常在其中,导致了那里。

Steve Howard 给出了一个很好的答案,我想将其扩展,不,减少 ... 仅限于 python 3。

except Exception as e:
    e.args = ("Some failure state", *e.args)
    raise

唯一的新东西是parameter expansion/unpacking,它使我使用起来足够小且容易。

试试看:

foo = None

try:
    try:
        state = "bar"
        foo.append(state)

    except Exception as e:
        e.args = ("Appending '"+state+"' failed", *e.args)
        raise

    print(foo[0]) # would raise too

except Exception as e:
    e.args = ("print(foo) failed: " + str(foo), *e.args)
    raise

这会给你:

Traceback (most recent call last):
  File "test.py", line 6, in <module>
    foo.append(state)
AttributeError: ('print(foo) failed: None', "Appending 'bar' failed", "'NoneType' object has no attribute 'append'")

一个简单的漂亮打印可能是这样的

print("\n".join( "-"*i+" "+j for i,j in enumerate(e.args)))

【讨论】:

  • 这有一个缺点是外部框架不是堆栈跟踪的一部分。
【解决方案5】:

我使用的一种方便的方法是使用类属性作为详细信息的存储,因为类属性可以从类对象和类实例中访问:

class CustomError(Exception):
    def __init__(self, details: Dict):
        self.details = details

然后在你的代码中:

raise CustomError({'data': 5})

当发现错误时:

except CustomError as e:
    # Do whatever you want with the exception instance
    print(e.details)

【讨论】:

  • 不是很有用,因为当原始异常被抛出但未被捕获时,OP 要求将详细信息作为堆栈跟踪的一部分打印出来。
  • 我认为解决方案很好。但描述不正确。当您实例化它们时,类属性会被复制到实例中。因此,当您修改实例的属性“详细信息”时,类属性仍将为无。无论如何,我们希望这里有这种行为。
【解决方案6】:

每当我想向异常添加额外信息时,我将提供我经常使用的代码 sn-p。我在 Python 2.7 和 3.6 中都工作。

import sys
import traceback

try:
    a = 1
    b = 1j

    # The line below raises an exception because
    # we cannot compare int to complex.
    m = max(a, b)  

except Exception as ex:
    # I create my  informational message for debugging:
    msg = "a=%r, b=%r" % (a, b)

    # Gather the information from the original exception:
    exc_type, exc_value, exc_traceback = sys.exc_info()

    # Format the original exception for a nice printout:
    traceback_string = ''.join(traceback.format_exception(
        exc_type, exc_value, exc_traceback))

    # Re-raise a new exception of the same class as the original one, 
    # using my custom message and the original traceback:
    raise type(ex)("%s\n\nORIGINAL TRACEBACK:\n\n%s\n" % (msg, traceback_string))

上面的代码产生以下输出:

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-6-09b74752c60d> in <module>()
     14     raise type(ex)(
     15         "%s\n\nORIGINAL TRACEBACK:\n\n%s\n" %
---> 16         (msg, traceback_string))

TypeError: a=1, b=1j

ORIGINAL TRACEBACK:

Traceback (most recent call last):
  File "<ipython-input-6-09b74752c60d>", line 7, in <module>
    m = max(a, b)  # Cannot compare int to complex
TypeError: no ordering relation is defined for complex numbers


我知道这与问题中提供的示例略有不同,但我希望有人觉得它有用。

【讨论】:

    【解决方案7】:

    与之前的答案不同,这适用于非常糟糕的__str__ 的异常。 但是,它确实修改了类型,以排除无用的 __str__ 实现。

    我仍然想找到一个不修改类型的额外改进。

    from contextlib import contextmanager
    @contextmanager
    def helpful_info():
        try:
            yield
        except Exception as e:
            class CloneException(Exception): pass
            CloneException.__name__ = type(e).__name__
            CloneException.__module___ = type(e).__module__
            helpful_message = '%s\n\nhelpful info!' % e
            import sys
            raise CloneException, helpful_message, sys.exc_traceback
    
    
    class BadException(Exception):
        def __str__(self):
            return 'wat.'
    
    with helpful_info():
        raise BadException('fooooo')
    

    保留原始回溯和类型(名称)。

    Traceback (most recent call last):
      File "re_raise.py", line 20, in <module>
        raise BadException('fooooo')
      File "/usr/lib64/python2.6/contextlib.py", line 34, in __exit__
        self.gen.throw(type, value, traceback)
      File "re_raise.py", line 5, in helpful_info
        yield
      File "re_raise.py", line 20, in <module>
        raise BadException('fooooo')
    __main__.BadException: wat.
    
    helpful info!
    

    【讨论】:

      【解决方案8】:

      您可以定义自己的异常,从另一个继承并创建它自己的构造函数来设置值。

      例如:

      class MyError(Exception):
         def __init__(self, value):
           self.value = value
           Exception.__init__(self)
      
         def __str__(self):
           return repr(self.value)
      

      【讨论】:

      • 不需要对原始异常的message 进行更改/附加某些内容(但我认为可以修复)。
      【解决方案9】:

      这是我的实现,将其用作上下文管理器并可选地向异常​​添加额外消息:

      from typing import Optional, Type
      from types import TracebackType
      
      class _addInfoOnException():
          def __init__(self, info: str = ""):
              self.info = info
      
          def __enter__(self):
              return
      
          def __exit__(self,
                       exc_type: Optional[Type[BaseException]],
                       exc_val: BaseException,  # Optional, but not None if exc_type is not None
                       exc_tb: TracebackType):  # Optional, but not None if exc_type is not None
              if exc_type:
                  if self.info:
                      newMsg = f"{self.info}\n\tLow level error: "
                      if len(exc_val.args) == 0:
                          exc_val.args = (self.info, )
                      elif len(exc_val.args) == 1:
                          exc_val.args = (f"{newMsg}{exc_val.args[0]}", )
                      elif len(exc_val.args) > 0:
                          exc_val.args = (f"{newMsg}{exc_val.args[0]}", exc_val.args[1:])
                  raise
      

      用法:

      def main():
          try:
              raise Exception("Example exception msg")
          except Exception:
              traceback.print_exc()
              print("\n\n")
      
          try:
              with _addInfoOnException():
                  raise Exception("Example exception msg, no extra info")
          except Exception:
              traceback.print_exc()
              print("\n\n")
      
          try:
              with _addInfoOnException("Some extra info!"):
                  raise Exception("Example exception msg")
          except Exception:
              traceback.print_exc()
              print("\n\n")
      
      
      if __name__ == "__main__":
          main()
      

      这将在此类回溯中解决:

      Traceback (most recent call last):
        File "<...>\VSCodeDevWorkspace\testis.py", line 40, in main
          raise Exception("Example exception msg")
      Exception: Example exception msg
      
      
      
      Traceback (most recent call last):
        File "<...>\VSCodeDevWorkspace\testis.py", line 47, in main
          raise Exception("Example exception msg, no extra info")
        File "<...>\VSCodeDevWorkspace\testis.py", line 47, in main
          raise Exception("Example exception msg, no extra info")
      Exception: Example exception msg, no extra info
      
      
      
      Traceback (most recent call last):
        File "<...>\VSCodeDevWorkspace\testis.py", line 54, in main
          raise Exception("Example exception msg")
        File "<...>\VSCodeDevWorkspace\testis.py", line 54, in main
          raise Exception("Example exception msg")
      Exception: Some extra info!
              Low level error: Example exception msg
      

      【讨论】:

        【解决方案10】:

        也许

        except Exception as e:
            raise IOError(e.message + 'happens at %s'%arg1)
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2010-09-08
          • 2012-12-12
          • 2011-02-18
          • 2014-11-03
          • 2011-05-01
          • 2014-01-19
          • 2019-11-14
          • 2012-04-13
          相关资源
          最近更新 更多