2

I have a python script, that has a long runtime. Sometimes I need to abort it and run it later. It dumps the current results in a pickle file, but while aborting (CTLR + C) at the wrong time that file gets corrupted.

Is there a way to let the script finish that task and abort it afterwards? I don't know where to look for.

Thank you

Edit: My program looks somewhat like this:

import pickle


for key in keylist:
    do_smth(mydict)
    with open('myfile.p','w+b') as f:
       pickle.dump(mydict,f)

Edit2: Thank you guys, try: ... except: ... works like a charm. Since I am the only user of the script I won't need the "save" version. However I will definitely look into it (right now I am not familiar with threading).

I also changed my loop, that I will only pickle my file, in case of an exception or after the loop finished.

import pickle

for key in keylist:
    try:
        do_smth(mydict)
    except KeyboardInterrupt:
         print("Saving data ...")
         with open('myfile.p','w+b') as f:
             pickle.dump(mydict,f)

     with open('myfile.p','w+b') as f:
         pickle.dump(mydict,f)
Ali
  • 761
  • 1
  • 5
  • 24

3 Answers3

3

Ctrl+C actually throws a special type of exception called KeyboardInterrupt. So, if your way of aborting the script to run it later is sending that exception, you can save your data before exiting. This can be done by wrapping your code in a try-except block and catch the KeyboardInterrupt exception.
An example would look like this:

try:
    # your main code here
except KeyboardInterrupt:
    # do the saving here
    exit(-1)   # exit the program with the return code -1

About the comment: make sure that you send this exception once.
It's because if you send it, the code will go to the saving part, that does not have the exception-catching block. So if you send it multiple times, your data may be saved improperly.

illright
  • 3,991
  • 2
  • 29
  • 54
  • however, the saving might be interrupted by CTRL+C, again – DomTomCat Jun 10 '16 at 07:30
  • @DomTomCat No, it can't. The exception is generated by the user, who is interested in keeping his data. So I doubt that he will send it multiple times. Of course, it's a possibility, but then it would be simpler to just advice the user. The simpler, the better – illright Jun 10 '16 at 07:34
  • 2
    well it still can and will - at the latest when someone else uses the code! but yes, they'll probably be careful, right. But also the data can get corrupt, too - e.g. it's just being updated when CTRL+C is invoked – DomTomCat Jun 10 '16 at 07:34
3

You want to protect two operations:

  1. the update of your data (not necessarily the computation)
  2. the writing of your data, once CTRL+C has been hit

With a try, except block only, both of these can simply be interrupted and leave your data or outputfile in a corrupted state.

This answer has an interesting comment:

Python threads cannot be interrupted except with a special C api.

So what you may want to do is to catch KeyboardInterrupt in your code and within this except branch, start a thread which writes out the current data. I've made an example:

import time
from threading import Thread

def heavy(n):
    for i in range(10000):
        time.sleep(1)
        print("doing some computation")

def noInterrupt(path, obj):
    try:
        print("interrupted, writing out data (try to press CTRL+C again)...")
        for i in range(5):
            time.sleep(1)
            print("...wrote block %d/5" % i)
    finally:
        print("DONE writing file")


mydata = []
try:
    # press CTRL+C somewhere here
    # manipulate data
    for i in range(1000):
        heavy(i)
except KeyboardInterrupt:
    a = Thread(target=noInterrupt, args=("path/to/file", mydata))
    a.start()
    a.join()

NOTE: Don't forget, the manipulation of your data can get corrupted with KeyboardInterrupt, too, so you might want to make sure you protect the process of updating your data (with another thread?)

Community
  • 1
  • 1
DomTomCat
  • 8,189
  • 1
  • 49
  • 64
  • By the way, `print(heavy(i))` will output both `"doing some computation"` and `"None"`, since you're printing the return value of the function `heavy` – illright Jun 10 '16 at 08:15
0

You can also create a handler for KeyboardInterrupt:

import signal
import sys
def signal_handler(signal, frame):
        #make sure you save the file
        sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
shiva
  • 2,535
  • 2
  • 18
  • 32