sys.excepthook 是Python解释器处理未捕获异常的顶层回调,仅捕获主线程中未被try/except拦截的异常,不处理SystemExit、KeyboardInterrupt、GeneratorExit等特殊异常。

sys.excepthook 是什么,它能捕获哪些异常
sys.excepthook 是 Python 解释器在未被捕获的异常冒泡到顶层时调用的回调函数。它只处理「未被 try/except 拦截」的异常,且仅对主线程生效。子线程中抛出的异常不会触发它,除非你显式调用 sys.excepthook(比如在 threading.Thread.run 里手动兜底)。
它不捕获 SystemExit、KeyboardInterrupt(默认行为)、GeneratorExit 这类特殊异常——这些是解释器保留信号,绕过 excepthook 直接终止或中断。
如何安全地重写 sys.excepthook
直接赋值 sys.excepthook = my_handler 很简单,但容易掩盖关键信息或引发二次异常。稳妥做法是包装原函数,而不是完全替换:
import sys
import traceback
<p>def my_excepthook(exc_type, exc_value, exc_traceback):</p><div class="aritcle_card flexRow">
<div class="artcardd flexRow">
<a class="aritcle_card_img" href="/xiazai/skill12569" title="Biopython"><img
src="https://img.php.cn/upload/skill/001/246/273/6adaf20e695c2374.jpg" alt="Biopython" ></a>
<div class="aritcle_card_info flexColumn">
<a href="/xiazai/skill12569" title="Biopython">Biopython</a>
<p>全面的分子生物学工具集。适用于序列操作、文件解析(FASTA/GenBank/PDB)、系统发育分析,以及通过 Bio.Entrez 以编程方式访问 NCBI/</p>
</div>
<a href="/xiazai/skill12569" title="Biopython" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span> </a>
</div>
</div><p><span>立即学习</span>“<a href="https://pan.quark.cn/s/00968c3c2c15" style="text-decoration: underline !important; color: blue; font-weight: bolder;" rel="nofollow" target="_blank">Python免费学习笔记(深入)</a>”;</p><h1>先记录原始信息(避免 print 可能失败)</h1><pre class="brush:php;toolbar:false;">with open("error.log", "a") as f:
f.write("Uncaught exception:\n")
traceback.print_exception(exc_type, exc_value, exc_traceback, file=f)
# 再调用原 hook(比如输出到 stderr)
sys.__excepthook__(exc_type, exc_value, exc_traceback)sys.excepthook = my_excepthook
- 永远优先调用
sys.__excepthook__而不是sys.excepthook,防止递归调用 - 避免在 handler 中使用可能失败的操作:比如网络请求、复杂日志库、未初始化的全局对象
- 不要在 handler 里 raise 新异常,否则会静默丢弃原始错误
- 如果要加额外字段(如时间戳、进程 ID),建议用
traceback.format_exception获取字符串再拼接,而非直接改参数
为什么 logging.exception() 在 excepthook 里可能不生效
因为 logging.exception() 本质是 logging.error(..., exc_info=True),它依赖当前线程的 logging 配置已就绪。而 sys.excepthook 可能在 logging 尚未配置(比如 basicConfig 没调用)或 handler 初始化失败时执行,导致日志无声丢失。
- 推荐用
traceback.format_exception()+ 手动写文件,更可控 - 若坚持用 logging,请确保在设置
sys.excepthook前已调用logging.basicConfig(),且 handler 不依赖外部服务(如SMTPHandler) - 注意:
logging.exception()会自动抓取当前sys.exc_info(),但在excepthook参数里已有三元组,无需再调用sys.exc_info()
多线程下怎么让 excepthook 生效
Python 默认不把子线程异常交给 sys.excepthook。常见做法是给线程设置 threading.excepthook(Python 3.8+)或包装 run 方法:
# Python 3.8+
def thread_excepthook(args):
# args 是 threading.ExceptHookArgs 对象
sys.excepthook(args.exc_type, args.exc_value, args.exc_traceback)
<p>threading.excepthook = thread_excepthook
- 低于 3.8 的版本必须手动包装:重写
Thread.run或用装饰器捕获异常后调用sys.excepthook - 协程(asyncio)中的异常更特殊:
asyncio.create_task()抛出的异常默认被静默丢弃,需用asyncio.get_event_loop().set_exception_handler()单独处理 - 即使设置了线程 hook,也不能假设所有异常都会“可见”——比如 daemon 线程崩溃时进程可能直接退出,来不及执行 handler
真正难的不是设 hook,而是保证它在各种崩溃边缘仍能稳定写出错误现场。磁盘满、权限不足、stderr 被重定向、甚至 print 函数本身被 monkey patch —— 这些都得提前想好 fallback。

















