try-except 是首选方案,因为 keyboardinterrupt 是继承自 baseexception 的内置异常,主线程收到 sigint 时自动抛出,天然适配异常机制;它跨平台、无需注册信号处理器、不干扰标准库中断逻辑,且能捕获任意深度调用中的中断。

Ctrl+C 触发的 KeyboardInterrupt 无法被普通信号处理器完全绕过,但用 try-except 捕获它是最直接、最可靠的方式实现优雅退出——别碰 signal.signal(signal.SIGINT, ...),除非你明确需要在多线程中自定义中断逻辑。
为什么 try-except 是首选方案
Python 的 KeyboardInterrupt 是一个内置异常,继承自 BaseException,它在主线程收到 SIGINT(即 Ctrl+C)时自动抛出。这使得它天然适配 Python 的异常处理机制,无需注册信号处理器,也不依赖平台行为差异。
-
try-except简单、直观、跨平台一致;signal.signal在 Windows 上对子线程无效,且容易与threading或asyncio冲突 - 只要
KeyboardInterrupt发生在try块内(包括其调用的任意深度函数),就能被捕获 - 不会干扰标准库中已有的中断处理逻辑(如
input()、time.sleep()等)
如何在循环中安全捕获并清理资源
常见场景是长运行的主循环(比如日志监听、轮询任务),需确保 Ctrl+C 后仍能执行关闭文件、断开连接等收尾动作。
- 把整个主逻辑包进
try,except KeyboardInterrupt:放在最外层,避免遗漏 - 清理代码必须写在
finally或except块里——不能只靠atexit.register(),因为它不保证在KeyboardInterrupt中执行 - 若使用
with语句管理资源(如文件、数据库连接),它们会在异常传播前自动__exit__,但仍建议显式 close 以防中间状态残留
import time
<p>try:
print("按 Ctrl+C 停止...")
while True:
time.sleep(1)
print("working...")
except KeyboardInterrupt:
print("\n正在退出...")</p><h1>这里做清理:close socket, flush log, save state...</h1><pre class="brush:php;toolbar:false;"><code>print("已清理,再见。")</code>
在多线程程序中要注意什么
KeyboardInterrupt 只会由主线程接收并抛出,子线程不会自动中断——这意味着仅靠主线程的 try-except 不足以让所有工作线程及时响应。
- 子线程应定期检查某个共享标志位(如
threading.Event),主线程捕获KeyboardInterrupt后设置该标志 - 避免在子线程中阻塞调用(如
queue.get()、time.sleep())而不设超时,否则可能卡住无法响应 - 不要在子线程里单独
try-except KeyboardInterrupt——它根本不会触发
asyncio 程序中的特殊处理
在 asyncio.run() 启动的事件循环中,Ctrl+C 默认会取消所有 pending task 并抛出 KeyboardInterrupt,但它发生在 run() 内部,外部 try-except 无效。
- 正确做法是用
asyncio.run()包裹入口函数,并在该函数内部用try-except - 如果用了
loop.run_forever(),需手动调用loop.stop()和loop.close(),并在except中完成 - 注意:
asyncio.CancelledError是 task 被取消时抛出的,不是KeyboardInterrupt,二者要区分处理
import asyncio
<p>async def main():
try:
while True:
await asyncio.sleep(1)
print("running...")
except KeyboardInterrupt:
print("\n收到中断,正在退出...")</p><h1>清理异步资源:cancel tasks, close aiohttp session...</h1><p>if <strong>name</strong> == "<strong>main</strong>":
asyncio.run(main()) # KeyboardInterrupt 会在此处终止 run()
</p>
真正容易被忽略的是:当程序 fork 出子进程或启动了 subprocess 时,Ctrl+C 默认会同时向父子进程发送 SIGINT,可能导致子进程意外退出。如果需要控制这种行为,得在 subprocess.Popen 中设置 start_new_session=True 或手动处理子进程信号组。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











