Python: 在异常实例中重写__str__

6 投票
2 回答
9895 浏览
提问于 2025-04-16 17:10

我正在尝试在Python中覆盖一个异常子类的打印输出,也就是在异常被抛出后想要改变它的显示方式,但我一直没能成功,让我覆盖的部分真正被调用。

def str_override(self):
    """
    Override the output with a fixed string
    """

    return "Override!"

def reraise(exception):
    """
    Re-raise an exception and override its output
    """

    exception.__str__ = types.MethodType(str_override, exception, type(exception))

    # Re-raise and remove ourselves from the stack trace.
    raise exception, None, sys.exc_info()[-1]

def test():
    """
    Should output "Override!" Actually outputs "Bah Humbug"
    """
    try:
        try:
            raise Exception("Bah Humbug")
        except Exception, e:
            reraise(e, "Said Scrooge")
    except Exception, e:
        print e

你知道为什么这并没有真正覆盖str方法吗?我查看了实例变量,发现这个方法确实被覆盖了,但就像Python就是不愿意通过打印来调用它一样。

我这里漏掉了什么呢?

2 个回答

2

http://docs.python.org/reference/datamodel.html#special-method-lookup-for-new-style-classes 说的是:“对于新式类,隐式调用特殊方法只有在这些方法定义在对象的类型上时才能保证正确工作,而不是在对象的实例字典里。”换句话说,你不能仅仅把一个方法直接赋值给 some_instance.__str__。另外,猴子补丁(Monkey Patching)在内置类型上,比如异常,是不管用的。其实你也不想这样做,即使是非内置的异常类,因为这样会改变该类所有实例的行为。

如果你觉得有点黑科技,你可以尝试做一些像这样的事情:

...
except DaUncoolException, e:
    e.args = ('cool override stuff!',) + e.args[1:]
    raise

不过我不太喜欢这样做。你为什么要做这种事情呢?

13

问题不是说 __str__() 没有被重写(就像你已经说过的,它确实被重写了),而是 str(e)(这个在打印时会隐式调用)并不总是等同于 e.__str__()。更具体来说,如果我理解得没错,str()(还有其他一些特殊方法,比如 repr())不会在实例字典里查找str,它只会在类字典里查找。至少在所谓的新式类中是这样的(如果我没记错的话,这些是Python 3.x中唯一的类)。你可以在这里了解更多信息:

http://mail.python.org/pipermail/python-bugs-list/2005-December/031438.html

如果你想改变重新抛出的异常的错误信息,你可以尝试这样做:

def reraise(exception):
    """
    Re-raise an exception and override its output
    """

    exType = type(exception)
    newExType = type(exType.__name__ + "_Override", (exType,), { '__str__': str_override})
    exception.__class__ = newExType

    # Re-raise and remove ourselves from the stack trace.
    raise exception, None, sys.exc_info()[-1]

这样会动态生成一个新的异常类,并重写str,然后将异常改为这个类的实例。现在你的代码应该可以正常工作了。

撰写回答