KeyboardInterrupt 是用户按 Ctrl+C 时主线程抛出的继承自 BaseException 的异常,因不属 Exception 类系,故 except Exception 捕获不到;须显式用 except KeyboardInterrupt 处理,并配合资源清理、线程退出信号或异步取消机制确保安全退出。

KeyboardInterrupt 是什么,为什么不能直接用 try-except 包住主逻辑
KeyboardInterrupt 是用户按 Ctrl+C 时 Python 主线程抛出的异常,它继承自 BaseException(而非 Exception),所以用普通 except Exception: 捕不到。很多新手写成这样:
try:
while True:
time.sleep(1)
except Exception: # ❌ 不会捕获 KeyboardInterrupt
print("退出")
结果是 Ctrl+C 依然直接终止程序,甚至可能留下未清理的资源。
正确捕获方式:用 except KeyboardInterrupt
必须显式写出 except KeyboardInterrupt:,且建议放在最外层或关键循环中。常见安全退出结构如下:
- 主循环里捕获,做清理后
break或return - 避免在
finally块里再抛异常(否则会掩盖KeyboardInterrupt) - 若使用多线程,
KeyboardInterrupt只发给主线程,子线程需自行检查退出信号
import time
<p>def main():
try:
while True:
print("运行中...")
time.sleep(1)
except KeyboardInterrupt:
print("\n正在退出...")</p><h1>这里放清理逻辑:关闭文件、断开连接、保存状态等</h1><pre class="brush:python;toolbar:false;"> cleanup()
return
def cleanup(): print("已释放资源")
在多线程/异步场景下怎么处理
主线程收到 KeyboardInterrupt 后,子线程默认不会自动停止,容易导致程序卡在 join() 或残留后台任务。解决要点:
- 用
threading.Event作为全局退出信号,各线程轮询检查 - 避免在子线程中直接捕获
KeyboardInterrupt(它收不到) - 异步代码(如
asyncio)需用asyncio.CancelledError配合signal模块监听SIGINT
简单线程示例:
import threading
import time
<p>stop_event = threading.Event()</p><p>def worker():
while not stop_event.is_set():
print("工作线程运行中...")
time.sleep(0.5)</p><p>t = threading.Thread(target=worker)
t.start()</p><p>try:
t.join() # 主线程等待,但可被 Ctrl+C 中断
except KeyboardInterrupt:
print("\n触发退出...")
stop_event.set() # 通知子线程退出
t.join(timeout=2) # 等待最多 2 秒</p>
容易忽略的细节:信号与子进程、资源泄漏
即使捕获了 KeyboardInterrupt,以下情况仍可能导致不安全退出:
- 在
with open(...)外部手动打开文件但没在except里close() - 调用了阻塞式 C 扩展(如某些数据库驱动),可能无法及时响应中断
- 子进程(
subprocess.Popen)未显式terminate()或kill() - 使用
os.system()时,Ctrl+C可能只杀掉 shell 而非真正目标进程
推荐做法:所有外部资源尽量走上下文管理器;子进程启动后存入列表,退出前统一 .terminate() 并 .wait()。
复杂点在于,不是所有阻塞调用都响应中断——比如 time.sleep() 会,但某些网络 I/O 在无超时设置时可能不响应,得靠超时 + 轮询 stop_event 来兜底。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











