每当抛出异常时调用钩子函数

15 投票
4 回答
10647 浏览
提问于 2025-04-15 12:25

假设我想在我的程序中,每当出现任何错误时,都能把这些错误记录到一个文件里。我不想去改动现有的代码。

当然,这个想法可以更广泛地理解为:每当发生错误时,都能插入一个处理的钩子。

那么,下面的代码是否可以安全地实现这个功能呢?

class MyException(Exception):

    def my_hook(self):
        print('---> my_hook() was called');

    def __init__(self, *args, **kwargs):
        global BackupException;
        self.my_hook();
        return BackupException.__init__(self, *args, **kwargs);


def main():
    global BackupException;
    global Exception;

    BackupException = Exception;
    Exception = MyException;

    raise Exception('Contrived Exception');


if __name__ == '__main__':
    main();

4 个回答

7

这段代码不会影响在main开始之前创建的任何异常类,而且大多数发生的异常都是这种类型的(比如KeyErrorAttributeError等等)。而且你实际上无法在最重要的意义上影响这些“内置异常”——如果你的代码中有像1/0这样的操作,真正的ZeroDivisionError会被抛出(这是Python内部处理的),而不是你可能绑定到那个异常名称的其他东西。

所以,我觉得你的代码无法实现你想要的效果(尽管有很多分号,它还是应该是Python,对吧?)——如果想要实现这个功能,基本上需要修改Python运行时的C源代码(例如,提供一个钩子,可以捕捉到任何异常,即使它后来被捕获了)——目前并没有这样的钩子,因为这种用例非常少见(比如,StopIteration总是在每个for循环正常结束时被抛出,并且也会被捕获;为什么会有人想要追踪这个,以及Python内部和标准库中许多其他常规使用的捕获异常呢?!)。

10

根据我所了解,你的代码可能是行不通的。

  1. __init__ 方法应该返回 None,但你却想返回一个备份异常的实例。一般来说,如果你想在创建类的实例时改变返回的对象,你应该重写 __new__ 方法。

  2. 不幸的是,你不能改变 Exception 类的任何属性。如果可以的话,你可以修改 Exception.__new__,然后在里面放置你的钩子。

  3. 使用 "global Exception" 的方法只对当前模块中的代码有效。Exception 是一个内置类,如果你真的想全局改变它,你需要用 import __builtin__; __builtin__.Exception = MyException

  4. 即使你改变了 __builtin__.Exception,这也只会影响未来对 Exception 的使用,已经定义的子类仍然会使用原来的 Exception 类,不会受到你的改变影响。你可以遍历 Exception.__subclasses__,然后为每个子类改变 __bases__,把你的 Exception 子类插入进去。

  5. 有些 Exception 的子类也是内置类型,你也无法修改,虽然我不确定你是否想要修改它们(比如 StopIteration)。

我认为,想要实现你想要的功能,唯一合适的方法就是修改 Python 的源代码。

21

如果你想记录那些没有被处理的错误,可以使用 sys.excepthook

我不太明白为什么要记录所有抛出的错误,因为很多库会自己处理一些错误,这些错误可能对你来说并不重要。

撰写回答