sys.excepthook可替换默认异常处理器,接收exc_type、exc_value、exc_traceback三参数,仅作用于主线程未捕获异常;子线程需用threading.excepthook(Python 3.8+),其函数签名仅接受一个threading.ExceptHookArgs参数。

用 sys.excepthook 替换默认异常处理器
Python 默认会在未捕获异常时打印 traceback 并退出程序,但你可以通过重写 sys.excepthook 来接管这个行为。它接收三个参数:exc_type、exc_value、exc_traceback,对应异常的类型、实例和 traceback 对象。
注意:这仅影响主线程中未被 try/except 捕获的异常,不处理线程内异常(需配合 threading.excepthook)或信号中断。
- 必须在程序启动早期设置,例如
if __name__ == "__main__":开头就赋值,否则可能错过早期异常 - 不要在 hook 函数里抛出新异常,否则会触发默认 handler,导致静默失败
- 若想保留原行为(比如仍打印 traceback),可手动调用
sys.__excepthook__(...)
import sys
<p>def my_excepthook(exc_type, exc_value, exc_tb):
print(f"[全局异常] {exc_type.<strong>name</strong>}: {exc_value}")</p><h1>可选:记录到文件、上报监控、发送告警</h1><pre class="brush:php;toolbar:false;"># 但别 raise 或 return 错误值 —— 这函数无返回值sys.excepthook = my_excepthook
捕获子线程中的未处理异常
sys.excepthook 对 threading.Thread 启动的子线程无效。Python 3.8+ 提供了 threading.excepthook,它接受一个 threading.ExceptHookArgs 对象,而不是原始三元组。
常见错误是直接复用 sys.excepthook 函数签名,结果报错 TypeError: excepthook() takes 3 positional arguments but 1 was given。
立即学习“Python免费学习笔记(深入)”;
- 必须定义符合
threading.ExceptHookArgs签名的函数(单个命名参数) - 可通过
args.exc_type、args.exc_value、args.exc_traceback访问异常信息 - 若使用旧版 Python(run() 方法或使用
concurrent.futures的回调机制
import threading
<p>def thread_excepthook(args):
print(f"[线程异常] {args.exc_type.<strong>name</strong>}: {args.exc_value}")</p><p>threading.excepthook = thread_excepthook
异步任务(asyncio)中的全局异常陷阱
asyncio 的任务(Task)抛出未捕获异常时,不会触发 sys.excepthook,而是由事件循环在垃圾回收时默默丢弃,并发出警告:Exception was never retrieved。
真正有效的拦截点是事件循环的 set_exception_handler,但它只对 asyncio.Task 生效,不覆盖普通协程或 asyncio.create_task() 外的异常。
- 必须在事件循环创建后、首次
create_task()前设置 handler - handler 函数接收两个参数:
loop和context;异常对象藏在context["exception"]中 - 若未设置 handler,异常会被静默吞掉 —— 这是生产环境最常漏掉的异常丢失场景
import asyncio
<p>def async_exception_handler(loop, context):
exc = context.get("exception")
if exc:
print(f"[async 异常] {type(exc).<strong>name</strong>}: {exc}")</p><p>loop = asyncio.get_event_loop()
loop.set_exception_handler(async_exception_handler)
为什么不能只靠 try/except 包裹 main()
看似把整个入口函数包进 try/except 就能兜住所有异常,但实际会漏掉几类关键情况:
- 信号处理函数(如
signal.signal(SIGUSR1, ...))中抛出的异常 - 对象析构(
__del__)期间发生的异常,会被忽略并输出Exception ignored in: ... - 多进程(
multiprocessing)子进程中异常,父进程看不到,除非显式join()+get() -
atexit注册函数中异常,同样被静默丢弃
所以真正健壮的全局异常捕获,是 sys.excepthook + threading.excepthook + loop.set_exception_handler 的组合,缺一不可。尤其在混合使用线程、协程和信号的长期运行服务中,少配一项就可能让故障悄无声息地发生。


















