【问题标题】:How to terminate loop gracefully when CTRL+C was pressed in python在python中按下CTRL + C时如何优雅地终止循环
【发布时间】:2014-08-17 01:35:08
【问题描述】:

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

这是我尝试过的。

a) Try-except 块

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()

输出:

Script started.
Processing file # 1 started...  finished.
Processing file # 2 started...  finished.
Processing file # 3 started... Bye
x= 3

第 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()

输出:

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

Exiting by CTRL+C.

第 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()

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

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

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

我删除了“打印”语句中的逗号并添加了更多内容以查看迭代实际上已重新启动:

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()

输出是:

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

【问题讨论】:

    标签: python loops


    【解决方案1】:

    我会简单地使用一个异常处理程序,它会捕获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()
    

    编辑:因为它已经在 cmets 中被发现,所以这个答案对于原始海报来说并不令人满意,这是一个基于线程的解决方案:

    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()
    

    【讨论】:

    • 这段代码看起来不错,但行为却很奇怪。查看问题的更新。
    • 你确定重启了吗?我认为异常只是弄乱了标准输出,因为它是缓冲的(删除打印语句末尾的“,”)
    • 看起来确实如此。请参阅问题中的更新代码。我也在琢磨为什么会这样。
    • 请注意,您在x 递增之前按了Ctrl-C,然后您开始另一个循环,然后测试存储的异常并中断。另请注意,如果您因异常中断代码,则不能简单地继续先前的计算。你想延迟中断,所以使用信号之类的东西似乎是唯一的方法。
    • 如果您不想要不完整的结果而不是等待它,那么在 Ctrl-C 上丢弃当前文件也会更自然。在这种情况下,您不需要任何特别的东西。只需在捕获 KeyboardInterrupt 后清理当前文件即可。
    【解决方案2】:

    你可以写一个信号处理函数

    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
    

    按下 Ctrl+c 会发送一个 SIGINT 中断,它会输出:

    Processing file # 1 started...
    Processing file # 2 started...
    ^CI'll be back
    bye
    3
    

    【讨论】:

    • 你的代码真的允许当前迭代优雅地完成吗?我也无法重现您的行为:按下 CTRL+C 后,我看不到“再见”。只是异常信息。 (我在 win7-64bit 上使用“Python 2.7.6 :: Anaconda 2.0.0 (64-bit)”)。
    • @anandr,它是从我的终端复制的确切输出。我在 linux 上使用 python 2.7.7
    • 我不知道如何添加大代码块来评论,所以我更新了你的答案,以展示我在运行你的代码时看到的内容。
    【解决方案3】:

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

    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
    

    【讨论】:

    • 这应该是公认的答案,它完全符合 OP 的目标。
    猜你喜欢
    • 1970-01-01
    • 2018-07-11
    • 1970-01-01
    • 1970-01-01
    • 2013-03-24
    • 2011-01-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多