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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/24426451/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-19 04:35:48  来源:igfitidea点击:

How to terminate loop gracefully when CTRL+C was pressed in python

pythonloops

提问by anandr

I'm rather new to python and I'm stuck with the following problem. I have a script that processes files one-by-one and writes output into separate files according to input file name. Sometimes I need to break the script, but I'd like to let it finish processing current file and then terminate (to avoid result files with incomplete information). How to code this behavior in python?

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

Here is what I tried.

这是我尝试过的。

a) Try-except block

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

Output:

输出:

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

Iteration #3 is not finished gracefully.

迭代 #3 没有优雅地完成。

b) sys.excepthook

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

Output:

输出:

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

Exiting by CTRL+C.

Iteration #3 is not finished gracefully.

迭代 #3 没有优雅地完成。

UPD#1

更新#1

@mguijarr , I slightly modified code like this:

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

The output is (tested using "Python 2.7.6 :: Anaconda 2.0.0 (64-bit)" on Win7-64bit):

输出是(在 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

In this case iteration #3 was effectively restarted, which looks odd and is not a desired behavior. Is it possible to avoid this?

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

I removed commas in 'print' statements and added more stuff to see that iteration is actually restarted:

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

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

and the output is:

输出是:

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

采纳答案by mguijarr

I would simply use an exception handler, which would catch KeyboardInterruptand store the exception. Then, at the moment an iteration is finished, if an exception is pending I would break the loop and re-raise the exception (to let normal exception handling a chance to happen).

我会简单地使用一个异常处理程序,它会捕获KeyboardInterrupt并存储异常。然后,在迭代完成的那一刻,如果有异常挂起,我会中断循环并重新引发异常(让正常的异常处理有机会发生)。

This works (tested with Python 2.7):

这有效(使用 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()

EDIT:as it has been spotted in the comments, this answer is not satisfying for the original poster, here is a solution based on threads:

编辑:正如在评论中发现的那样,这个答案对原始海报并不满意,这是一个基于线程的解决方案:

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

回答by Ashoka Lella

You can write a signal handling function

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

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

pressing Ctrl+c sends a SIGINT interrupt which would output:

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

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

回答by Esben Folger Thomas

I feel that creating a class with a state that handles user exceptions is a bit more elegant since I don't have to mess with global variables that don't work across different modules

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

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