可靠捕获 KeyboardInterrupt 需以 finally 为主、atexit 为辅:finally 做核心清理并用标志位防重入,多线程下配合 Event 通知子线程退出,避免 os._exit() 跳过 finally,atexit 作为最后兜底。

捕获 KeyboardInterrupt 不能只靠 except KeyboardInterrupt
直接写 try...except KeyboardInterrupt 捕获不到所有中断场景——比如在多线程中主线程被 Ctrl+C 中断时,子线程可能仍在运行;又或者在 input()、time.sleep() 等阻塞调用中触发的 KeyboardInterrupt 会被立即抛出,但若此时正处在文件写入中途,except 块里没做清理,数据就可能损坏。
真正可靠的方案是:把关键清理逻辑统一收口到 finally,并确保 KeyboardInterrupt 不被意外吞掉。
-
except KeyboardInterrupt后不要写pass或空return,否则finally可能不执行(尤其在函数内) - 避免在
except中调用os._exit()或sys.exit(),它们会跳过finally - 若用
signal.signal(signal.SIGINT, handler)自定义处理,必须在 handler 里手动触发清理或重新抛出KeyboardInterrupt
finally 里做清理要防重入和状态不一致
finally 确保执行,但不保证只执行一次——比如嵌套 try、或异常传播途中多次进入 finally,都可能导致重复关闭文件、重复提交事务。
推荐做法是用标志位 + 守卫逻辑控制关键操作仅执行一次:
file_handle = None
cleanup_done = False
<p>try:
file_handle = open("data.tmp", "w")</p><h1>写入逻辑...</h1><pre class="brush:php;toolbar:false;">while True:
# 模拟长时间运行
time.sleep(1)
except KeyboardInterrupt: print("收到中断,准备退出...") raise # 重新抛出,让 finally 执行 finally: if not cleanup_done and file_handle: try: file_handle.close() os.replace("data.tmp", "data.json") # 原子提交 except OSError: pass # 关闭失败也别卡住 finally: cleanup_done = True
多线程下 KeyboardInterrupt 的特殊行为
Ctrl+C 默认只中断主线程,子线程继续跑。这意味着即使主线程进了 finally,子线程仍可能往同一个文件写数据,引发竞态或损坏。
- 用
threading.Event通知子线程退出,主线程在finally中先置位再join() - 避免子线程持有独占资源(如打开的文件句柄),应由主线程统一管理生命周期
- 若用
concurrent.futures.ThreadPoolExecutor,需配合shutdown(wait=True),但它不响应KeyboardInterrupt中断等待——得自己加超时和轮询
替代方案:用 atexit.register() 补充清理兜底
finally 在正常流程和大多数异常下可靠,但遇到 os._exit()、进程被 SIGKILL 杀死、或 C 扩展崩溃等情况时不会触发。atexit 是更底层的注册机制,适合做最后防线。
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
注意它不接收参数,也不支持返回值,且注册函数不能抛异常(否则整个进程退出失败):
import atexit
import os
<p>temp_file = "output.tmp"</p><p>def cleanup():
try:
if os.path.exists(temp_file):
os.remove(temp_file)
except OSError:
pass # 失败就放弃,不干扰退出流程</p><p>atexit.register(cleanup)</p><h1>主逻辑...</h1><p>with open(temp_file, "w") as f:
f.write("in progress...")
raise KeyboardInterrupt # 触发清理
</p>
真正难的不是写 finally,而是想清楚哪些状态必须原子化、哪些资源必须成对出现、以及中断发生时系统到底停在哪一行——这些细节往往藏在日志之外,只在数据损坏后才暴露出来。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










