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

Question:

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?

Here is what I tried.

a) Try-except block

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.

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("nnExiting by CTRL+C.nn")
    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.

UPD#1

@mguijarr , I slightly modified code like this:

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

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?

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
Asked By: anandr

||

Answers:

I would simply use an exception handler, which would catch KeyboardInterrupt and
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).

This works (tested with 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()
Answered By: mguijarr

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:

Processing file # 1 started...
Processing file # 2 started...
^CI'll be back
bye
3
Answered By: Ashoka Lella

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
Answered By: Esben Folger Thomas

Late to the party, but this was my solution:

#!/usr/bin/env python
# -*- coding: utf8 -*-

import sys
import signal
import time

interrupted = False

def main():
    global interrupted
    oldHandler = signal.signal(signal.SIGINT, handle_interrupt)

    x = 1
    print "Script started"
    while True:
      print "Processing file #",x,"started...",
      # do something time-cosnuming
      time.sleep(1)
      x += 1
      print " finished."
      if interrupted:
        break

    signal.signal(signal.SIGINT, oldHandler)

def handle_interrupt(sig, frame):
    global interrupted
    interrupted = True

if __name__ == '__main__':
    sys.exit(main())
Answered By: Edward Falk
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.