5

I have a program which takes a long time to complete. I would like it to be able to catch SIGINT (ctrl-c) and call the self.save_work() method.

As it stands, my signal_hander() does not work since self is not defined by the time the program reaches signal_handler().

How can I set it up so self.save_work gets called after a SIGINT?

#!/usr/bin/env python
import signal 

def signal_handler(signal, frame):    
    self.save_work()   # Does not work
    exit(1)
signal.signal(signal.SIGINT, signal_handler)

class Main(object):
    def do_stuff(self):
        ...
    def save_work(self):
        ...
    def __init__(self):
        self.do_stuff()
        self.save_work()

if __name__=='__main__':
    Main()
unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677

3 Answers3

4

If you just want to catch ctr+c then you can catch the KeyboardInterrupt exception:

class Main(object):
    def do_stuff(self):
        ...
    def save_work(self):
        ...
    def __init__(self):
        try:
            self.do_stuff()
        except KeyboardInterrupt:
            pass # Or print helpful info
        self.save_work()

Not that I think this is a good design after all. It looks like you need to be using a function instead of a constructor.

Nadia Alramli
  • 111,714
  • 37
  • 173
  • 152
  • ... assuming KeyboardInterrupt is thrown. I had occasions where it wasn't. Yet, sigint could be handled. Had to do with threads and blocking operations as far as I can remember. Unless I had borked my code somewhere else ;) – exhuma Nov 04 '09 at 15:09
  • @exhuma, the OP case seems simple enough. And yes I think I've encountered this behavior before with threads. – Nadia Alramli Nov 04 '09 at 15:13
  • Thank you. KeyboardInterrupt is what I was looking for. Sometimes I use a class just to simplify the passing around of parameters, without using globals. Is this considered bad design? – unutbu Nov 04 '09 at 15:28
  • @~unutbu, In your case it is confusing. Main() is supposed to be creating an object. Yet in your case it is running the whole application. If you want to simplify the arguments passing you could use class methods and class variables instead of constructors. – Nadia Alramli Nov 04 '09 at 15:35
  • Thanks again. Would it be consider acceptable if I changed `__init__` to `run` and called `Main().run()`? – unutbu Nov 04 '09 at 15:51
3

Usually, "work" involves some kind of a big loop. To tame your loop, and prevent it from breaking in an unknown step, you can use the following context manager:

import signal

class GracefulInterruptHandler(object):

    def __init__(self, sig=signal.SIGINT):
        self.sig = sig

    def __enter__(self):

        self.interrupted = False
        self.released = False

        self.original_handler = signal.getsignal(self.sig)

        def handler(signum, frame):
            self.release()
            self.interrupted = True

        signal.signal(self.sig, handler)

        return self

    def __exit__(self, type, value, tb):
        self.release()

    def release(self):

        if self.released:
            return False

        signal.signal(self.sig, self.original_handler)

        self.released = True

        return True

To use:

import time

/// do stuff:
with GracefulInterruptHandler() as h:
    for i in xrange(1000):
        print "..."
        time.sleep(1)
        if h.interrupted:
            print "interrupted!"
            time.sleep(5)
            break

save_work()

From here: https://gist.github.com/2907502

Udi
  • 29,222
  • 9
  • 96
  • 129
2
import signal

def signal_handler(signal, frame):    
   #do some stuff

def main():
   #do some more stuff


if __name__=='__main__':
    signal.signal(signal.SIGINT, signal_handler)
    main()
jldupont
  • 93,734
  • 56
  • 203
  • 318