如何在python中按下CTRL + C时优雅地终止循环

ana*_*ndr 11 python loops

我对python很新,我遇到了以下问题.我有一个脚本,它逐个处理文件,并根据输入文件名将输出写入单独的文件.有时我需要打破脚本,但我想让它完成当前文件的处理,然后终止(以避免信息不完整的结果文件).如何在python中编写此行为?

这是我试过的.

a)尝试 - 除外块

x = 1
print "Script started."
while True:
 try:
  print "Processing file #",x,"started...",
  # do something time-cosnuming
  time.sleep(1)
  x += 1
  print " finished."
 except KeyboardInterrupt:
  print "Bye"
  print "x=",x
  sys.exit()

sys.exit()
Run Code Online (Sandbox Code Playgroud)

输出:

Script started.
Processing file # 1 started...  finished.
Processing file # 2 started...  finished.
Processing file # 3 started... Bye
x= 3
Run Code Online (Sandbox Code Playgroud)

迭代#3未正常完成.

b)sys.excepthook

OriginalExceptHook = sys.excepthook
def NewExceptHook(type, value, traceback):
global Terminator
    Terminator = True
    if type == KeyboardInterrupt:
        #exit("\nExiting by CTRL+C.")   # this line was here originally
        print("\n\nExiting by CTRL+C.\n\n")
    else:
        OriginalExceptHook(type, value, traceback)
sys.excepthook = NewExceptHook

global Terminator
Terminator = False

x = 1
while True:
  print "Processing file #",x,"started...",
  # do something time-cosnuming
  time.sleep(1)
  x += 1
  print " finished."
  if Terminator:
   print "I'll be back!"
   break

print "Bye"
print "x=",x
sys.exit()
Run Code Online (Sandbox Code Playgroud)

输出:

Script started.
Processing file # 1 started...  finished.
Processing file # 2 started...  finished.
Processing file # 3 started...

Exiting by CTRL+C.
Run Code Online (Sandbox Code Playgroud)

迭代#3未正常完成.

UPD#1

@mguijarr,我稍微修改了这样的代码:

import time, sys

x = 1
print "Script started."
stored_exception=None

while True:
    try:
        print "Processing file #",x,"started...",
        # do something time-cosnuming
        time.sleep(1)
        print "Processing file #",x,"part two...",
        time.sleep(1)
        print " finished."
        if stored_exception:
            break
        x += 1
    except KeyboardInterrupt:
        print "[CTRL+C detected]",
        stored_exception=sys.exc_info()

print "Bye"
print "x=",x

if stored_exception:
    raise stored_exception[0], stored_exception[1], stored_exception[2]

sys.exit()
Run Code Online (Sandbox Code Playgroud)

输出是(在Win7-64bit上使用"Python 2.7.6 :: Anaconda 2.0.0(64位)"测试):

Script started.
Processing file # 1 started... Processing file # 1 part two...  finished.
Processing file # 2 started... Processing file # 2 part two...  finished.
Processing file # 3 started... [CTRL+C detected] Processing file # 3 started... Processing file # 3 part two...  finished.
Bye
x= 3
Traceback (most recent call last):
  File "test2.py", line 12, in <module>
    time.sleep(1)
KeyboardInterrupt
Run Code Online (Sandbox Code Playgroud)

在这种情况下,迭代#3被有效地重新启动,这看起来很奇怪并且不是期望的行为.有可能避免这种情况吗?

我删除了'print'语句中的逗号并添加了更多内容以查看迭代实际重新启动:

import time, sys

x = 1
y = 0
print "Script started."
stored_exception=None

while True:
    try:
        y=x*1000
        y+=1
        print "Processing file #",x,y,"started..."
        y+=1
        # do something time-cosnuming
        y+=1
        time.sleep(1)
        y+=1
        print "Processing file #",x,y,"part two..."
        y+=1
        time.sleep(1)
        y+=1
        print " finished.",x,y
        y+=1
        if stored_exception:
            break
        y+=1
        x += 1
        y+=1
    except KeyboardInterrupt:
        print "[CTRL+C detected]",
        stored_exception=sys.exc_info()

print "Bye"
print "x=",x
print "y=",y

if stored_exception:
    raise stored_exception[0], stored_exception[1], stored_exception[2]

sys.exit()
Run Code Online (Sandbox Code Playgroud)

输出是:

Script started.
Processing file # 1 1001 started...
Processing file # 1 1004 part two...
 finished. 1 1006
Processing file # 2 2001 started...
Processing file # 2 2004 part two...
[CTRL+C detected] Processing file # 2 2001 started...
Processing file # 2 2004 part two...
 finished. 2 2006
Bye
x= 2
y= 2007
Traceback (most recent call last):
  File "test2.py", line 20, in <module>
    time.sleep(1)
KeyboardInterrupt
Run Code Online (Sandbox Code Playgroud)

mgu*_*arr 12

我只是使用一个异常处理程序,它将捕获KeyboardInterrupt并存储异常.然后,在迭代完成的那一刻,如果异常处于挂起状态,我将打破循环并重新引发异常(让正常的异常处理发生机会).

这工作(使用Python 2.7测试):

x = 1
print "Script started."
stored_exception=None

while True:
    try:
        print "Processing file #",x,"started...",
        # do something time-cosnuming
        time.sleep(1)
        print " finished."
        if stored_exception:
            break
        x += 1
    except KeyboardInterrupt:
        stored_exception=sys.exc_info()

print "Bye"
print "x=",x

if stored_exception:
    raise stored_exception[0], stored_exception[1], stored_exception[2]

sys.exit()
Run Code Online (Sandbox Code Playgroud)

编辑:因为它已经在评论中被发现,这个答案不满足原始海报,这是一个基于线程的解决方案:

import time
import sys
import threading

print "Script started."

class MyProcessingThread(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)

    def run(self):
        print "Processing file #",x,"started...",
        # do something time-cosnuming
        time.sleep(1)
        print " finished."

for x in range(1,4):
    task = MyProcessingThread()
    task.start()
    try:
        task.join()
    except KeyboardInterrupt:
        break

print "Bye"
print "x=",x

sys.exit()
Run Code Online (Sandbox Code Playgroud)

  • 请注意,在"x"递增之前按Ctrl-C,然后启动另一个循环,然后测试存储的异常并中断.另请注意,如果您通过异常中断代码,则不能简单地继续以前的计算.你想要延迟中断,所以使用像信号这样的东西似乎是唯一的方法. (3认同)
  • 如果你不想要不完整的结果而不是等待它,那么在Ctrl-C上丢弃当前文件也更自然.在这种情况下,您不需要任何特殊的东西.在捕获KeyboardInterrupt后,只需清理当前文件. (3认同)

小智 8

我觉得创建一个具有处理用户异常状态的类更优雅一些,因为我不必弄乱不能跨不同模块工作的全局变量

import signal
import time

class GracefulExiter():

    def __init__(self):
        self.state = False
        signal.signal(signal.SIGINT, self.change_state)

    def change_state(self, signum, frame):
        print("exit flag set to True (repeat to exit now)")
        signal.signal(signal.SIGINT, signal.SIG_DFL)
        self.state = True

    def exit(self):
        return self.state


x = 1
flag = GracefulExiter()
while True:
    print("Processing file #",x,"started...")
    time.sleep(1)
    x+=1
    print(" finished.")
    if flag.exit():
        break
Run Code Online (Sandbox Code Playgroud)


Ash*_*lla 6

您可以编写信号处理功能

import signal,sys,time                          
terminate = False                            

def signal_handling(signum,frame):           
    global terminate                         
    terminate = True                         

signal.signal(signal.SIGINT,signal_handling) 
x=1                                          
while True:                                  
    print "Processing file #",x,"started..." 
    time.sleep(1)                            
    x+=1                                     
    if terminate:                            
        print "I'll be back"                 
        break                                
print "bye"                                  
print x
Run Code Online (Sandbox Code Playgroud)

按下Ctrl + c会发送SIGINT中断,该中断将输出:

Processing file # 1 started...
Processing file # 2 started...
^CI'll be back
bye
3
Run Code Online (Sandbox Code Playgroud)