Python3触发KeyboardInterrupt时如何确保完成当前循环再退出
Python实现循环内foo/bar成对执行不被Ctrl+C中断的方案
核心要求是单轮循环里从foo(i)开始到bar(i)结束的整段逻辑是不可被KeyboardInterrupt打断的原子块,中断只会在两轮循环的间隙被响应,不管Ctrl+C是在foo运行时、foo和bar之间、还是bar运行时按下,都要等当前轮次全部跑完再退出。
方案1:自定义SIGINT信号处理(最可靠,完全满足需求)
Python默认收到Ctrl+C发送的SIGINT信号时,会在任意字节码执行间隙抛出KeyboardInterrupt,不管你代码写到哪都可能被打断。最稳妥的解决方式是临时替换信号处理逻辑:收到信号时先打个标记,不立刻抛异常,等整轮循环逻辑跑完再检查标记、响应中断。
直接上代码:
import signal # 中断标记,记录是否收到过Ctrl+C interrupted = False def handle_sigint(signum, frame): global interrupted interrupted = True # 先保存原来的SIGINT处理逻辑,退出前恢复 original_handler = signal.getsignal(signal.SIGINT) # 注册我们自己的临时处理函数 signal.signal(signal.SIGINT, handle_sigint) for i in range(10**6): # 下面这整段执行期间,按Ctrl+C只会改标记,不会打断执行 foo(i) print("foo executed with parameter", i) bar(i) print("bar executed with parameter", i) # 整轮跑完再检查有没有收到中断 if interrupted: # 恢复原信号处理 signal.signal(signal.SIGINT, original_handler) # 主动抛出键盘中断,和原生触发的行为完全一致 raise KeyboardInterrupt
这个方案没有逻辑漏洞:不管你是在foo跑一半、打印foo日志的时候、还是bar跑一半按Ctrl+C,都只会把
interrupted标记设为True,当前轮次的代码会完整跑完,之后才会触发中断退出,完全匹配需求。
方案2:try...except包裹整轮逻辑(仅适合简单场景)
如果你不想操作信号模块,也可以用try块捕获整轮迭代内的中断,捕获到之后补完剩余逻辑再重抛。但这个方案有明显局限性:如果中断是在foo执行过程中触发的,foo本身会被异常打断,没法保证foo执行完成,只能覆盖中断发生在foo和bar之间的场景。
代码示例:
for i in range(10**6): try: foo(i) print("foo executed with parameter", i) bar(i) print("bar executed with parameter", i) except KeyboardInterrupt: # 兜底补跑bar,注意如果中断是在foo内部触发的,foo已经被打断,不符合要求 bar(i) print("bar executed with parameter", i) raise
如果你的foo和bar都是执行速度极快、内部几乎不会被信号打断的小逻辑,这个方案也能用,但生产场景优先选方案1。
避坑提醒
不要只把try/except套在foo和bar中间的打印语句上,那样完全覆盖不到两个函数执行过程中收到中断的场景,实现不了要求的原子性。
内容的提问来源于stack exchange,提问作者Sergey
相关产品推荐
相关产品推荐

