起初,sys.excepthook 看起来像是一个解决方案。它是一个全局钩子,每次抛出未捕获的异常时都会调用它。
很遗憾,这不起作用。为什么?好吧,threading 将您的 run 函数包装在代码中,该函数会打印您在屏幕上看到的可爱回溯(注意它总是如何告诉您 Exception in thread {Name of your thread here}?这就是它的完成方式)。
从 Python 3.8 开始,您可以重写一个函数来完成这项工作:threading.excepthook
... threading.excepthook() 可以被覆盖以控制如何处理 Thread.run() 引发的未捕获异常
那么我们该怎么办?用我们的逻辑替换这个函数,voilà:
对于 python >= 3.8
import traceback
import threading
import os
class GlobalExceptionWatcher(object):
def _store_excepthook(self, args):
'''
Uses as an exception handlers which stores any uncaught exceptions.
'''
self.__org_hook(args)
formated_exc = traceback.format_exception(args.exc_type, args.exc_value, args.exc_traceback)
self._exceptions.append('\n'.join(formated_exc))
return formated_exc
def __enter__(self):
'''
Register us to the hook.
'''
self._exceptions = []
self.__org_hook = threading.excepthook
threading.excepthook = self._store_excepthook
def __exit__(self, type, value, traceback):
'''
Remove us from the hook, assure no exception were thrown.
'''
threading.excepthook = self.__org_hook
if len(self._exceptions) != 0:
tracebacks = os.linesep.join(self._exceptions)
raise Exception(f'Exceptions in other threads: {tracebacks}')
对于旧版本的 Python,这有点复杂。
长话短说,threading 结节似乎有一个未记录的导入,它的作用类似于:
threading._format_exc = traceback.format_exc
并不奇怪,这个函数只有在线程的run函数抛出异常时才会被调用。
所以对于 python
import threading
import os
class GlobalExceptionWatcher(object):
def _store_excepthook(self):
'''
Uses as an exception handlers which stores any uncaught exceptions.
'''
formated_exc = self.__org_hook()
self._exceptions.append(formated_exc)
return formated_exc
def __enter__(self):
'''
Register us to the hook.
'''
self._exceptions = []
self.__org_hook = threading._format_exc
threading._format_exc = self._store_excepthook
def __exit__(self, type, value, traceback):
'''
Remove us from the hook, assure no exception were thrown.
'''
threading._format_exc = self.__org_hook
if len(self._exceptions) != 0:
tracebacks = os.linesep.join(self._exceptions)
raise Exception('Exceptions in other threads: %s' % tracebacks)
用法:
my_thread = x.ExceptionRaiser()
# will fail when thread is started and raises an exception.
with GlobalExceptionWatcher():
my_thread.start()
my_thread.join()
您仍然需要自己join,但退出时,with-statement 的上下文管理器将检查其他线程中抛出的任何异常,并适当地引发异常。
代码按“原样”提供,不提供任何形式的保证,
明示或暗示
这是一个无证的、可怕的黑客攻击。我在linux和windows上测试过,似乎可以。使用它需要您自担风险。