26

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("\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.

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
Community
  • 1
  • 1
anandr
  • 1,622
  • 1
  • 12
  • 10

4 Answers4

17

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()
mguijarr
  • 7,641
  • 6
  • 45
  • 72
  • This code seems OK, but behaves strangely. See updates to the question. – anandr Jun 26 '14 at 09:34
  • Are you sure it is restarted? I think the exception is just messing up stdout because it is buffered (remove the "," at the end of your print statements) – mguijarr Jun 26 '14 at 09:36
  • Seems like it really is. See updated code in the question. I'm also wandering why it is like this. – anandr Jun 26 '14 at 09:51
  • 3
    Note that you hit Ctrl-C before `x` is incremented, then you start another loop and then test the stored exception and break. Also note that if you interrupt your code by exception, you cannot simply continue the previous computation. You want do delay the interrupt so using something like signal seems to be the only way. – user87690 Jun 26 '14 at 10:18
  • 3
    It would be also more natural to discard the current file on Ctrl-C if you don't want incomplete results rather than wait for it. In this case you wouldn't need anything special. Just do cleanup of current file after catching KeyboardInterrupt. – user87690 Jun 26 '14 at 10:22
  • +1 @user87690. Both for noting that exceptios are not regular events and for idea with discarding the last file – anandr Jun 26 '14 at 10:33
  • user87690 is right! I just updated my answer to provide another solution based on threads (just to give another idea, handling the SIGINT signal seems like a good idea too). – mguijarr Jun 26 '14 at 10:47
  • @mguijarr, I like the idea with threads too. The code behaves properly and is potentially extendable to mutithreading file processing, which is another nice thing to learn with python :) Also it seems that putting the `if stored_exception: break` statement at the very begin of the `try` block avoids restarting the current iteration. I'll check this effect in more details later. – anandr Jun 26 '14 at 10:54
14

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
shellcat_zero
  • 1,027
  • 13
  • 20
11

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
Ashoka Lella
  • 6,631
  • 1
  • 30
  • 39
  • Does your code really allow current iteration to finish gracefully? Also I can not reproduce your behavior: I do not see "bye" printed after CTRL+C was pressed. Just the exception info. (I'm using "Python 2.7.6 :: Anaconda 2.0.0 (64-bit)" on win7-64bit). – anandr Jun 26 '14 at 09:52
  • @anandr, Its the exact output copied from my terminal. I'm using python 2.7.7 on linux – Ashoka Lella Jun 26 '14 at 09:58
  • I do not know how to add big code blocks to comment so I updated your answer to demonstrate what I see when I run your code. – anandr Jun 26 '14 at 10:02
0

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())
Edward Falk
  • 9,991
  • 11
  • 77
  • 112
  • Note: I would probably wrap the bulk of it in a try…finally block to make sure that the handler gets restored no matter what. I left it out in the interest of simplicity. – Edward Falk Feb 27 '23 at 22:12
  • Additionally, the interrupt causes time.sleep() to return immediately; I don't know how to get around that. – Edward Falk Feb 27 '23 at 22:15