向异常添加信息?


142

我想实现以下目标:

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中做到这一点?


在寻找Exception message属性的文档时,我发现了这个问题,Python 2.6中不推荐使用BaseException.message,这似乎表明现在不鼓励使用它(以及为什么不在文档中)。
martineau 2011年

可悲的是,该链接似乎不再起作用。
Michael Scott Cuthbert

1
@MichaelScottCuthbert这里是一个很好的选择:itmaybeahack.com/book/python-2.6/html/p02/...
尼尔斯Keurentjes

这是对message属性的状态及其与args属性和PEP 352的关系的很好的解释。它来自史蒂文·F·洛特(Steven F. Lott)的免费书籍《Python中构建技能》
martineau 2014年

Answers:


118

我会这样做,因此更改它的类型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,我的第一次更新中的代码在语法上是不正确的,并且在message2012 BaseException年5 月16日对PEP 352的更改收回了启用属性的想法(我的第一次更新发布于2012-03-12) 。因此,当前,无论如何,在Python 3.5.2中,您都需要按照以下步骤做一些事情以保留回溯,而不是硬编码function中的异常类型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。工作虽然答案可能似乎是“不”,因为语法不同的方式,还有就是周围的一种方法,通过使用一个辅助函数一样reraise()six添加-在模块上。因此,如果您出于某种原因不愿使用该库,则下面是简化的独立版本。

还要注意,由于异常是在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')

3
这样就失去了追溯力,有点无法将信息添加到现有异常中。同样,使用ctor接受> 1个参数的异常也不起作用(类型是从捕获异常的地方无法控制的东西)。
瓦茨拉夫·斯拉维克

1
@Václav:防止丢失回溯很容易-如我添加的更新所示。尽管这仍然不能处理所有可能的异常,但它确实适用于类似于OP问题中所示的情况。
martineau 2012年

1
不太正确。如果type(e)覆盖__str__,您可能会得到不希望的结果。还要注意,第二个参数传递给第一个参数给定的构造函数,这会产生一些荒谬的type(e)(type(e)(e.message)。第三,不赞成使用e.message,而建议使用 e.args [0]。
2013年

1
因此,在Python 2和3中没有可移植的方法吗?
Elias Dorneles 2014年

1
@martineau在except块内导入的目的是什么?这是仅在必要时通过导入来节省内存吗?
2014年

114

如果您是来这里寻找Python 3解决方案的,该手册会 说:

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

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不弄乱原始Exception的情况下,将完全没有描述的信息变成带有解决方案提示的好消息。


14
这是最好的解决方案,因为产生的异常指向原始原因,请提供更多详细信息。
JT

有什么解决方案可以添加一些消息但仍然不会引发新的异常?我的意思是只扩展异常实例的消息。
edcSam

Yaa ~~可以,但是感觉有些事情不应该对我做。该消息存储在中e.args,但是它是一个元组,因此无法更改。因此,首先将其复制args到列表中,然后对其进行修改,然后将其复制回元组:args = list(e.args) args[0] = 'bar' e.args = tuple(args)
克里斯·

27

假设您不想或无法修改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吧?
anijhaw 2011年

1
如果捕获Exception(如上编辑),则可以捕获任何标准库异常(以及从Exception继承并调用Exception .__ init__的异常)。
史蒂夫·霍华德

6
为了更完整/更合作,包括原始元组的其他部分:e.args = ('mynewstr' + e.args[0],) + e.args[1:]
Dubslow

1
@ nmz787实际上,这是Python 3 的最佳解决方案。你到底是什么错误?
基督教徒

1
@Dubslow和martineau我将您的建议纳入了编辑。
基督教徒

9

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

我要拥有的只是指向源异常的stacktrace,中间没有异常的东西,因此不创建新的异常,只需重新引发具有所有相关堆栈框架状态的原始异常,就可以了。

史蒂夫·霍华德Steve Howard)给出了一个很好的答案,我想将其扩展为不,仅限于Python 3。

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

唯一的新功能是参数扩展/解压缩,它使它小巧易用。

试试吧:

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要求在引发原始异常并没有捕获到异常时将详细信息作为堆栈跟踪的一部分进行打印。
Cowbert

我认为解决方案很好。但是描述是不正确的。实例化类属性时,它们会复制到实例。因此,当您修改实例的属性“ details”时,class属性仍将为None。无论如何,我们希望在这里有这种行为。
亚当·沃纳

2

与先前的答案不同,这在面对非常糟糕的异常时有效__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!

2

每当我想向异常添加额外的信息时,我都会提供我经常使用的代码片段。我在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


我知道这与问题中提供的示例有些出入,但是我还是希望有人觉得它有用。


1

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

例如:

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

   def __str__(self):
     return repr(self.value)

2
不能解决需要对message原始异常进行更改/添加某些内容的问题(但我认为可以修复)。
martineau

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.