Ricky Wilson
Ricky Wilson

Reputation: 3359

How to prompt a user if they are sure they want to exit the program on a KeyboardInterrupt?

When I hit ctrl-c the prompt pops up on the screen and I input no because I want to keep running the program but the program exits anyway. Where am I going wrong? Is this functionality even possible?

from functools import wraps
import sys

class CleanExit(object):
    def __init__(self, *args, **kw):
        # You can supply an optional function
        # to be called when the KeyboardInterrupt
        # takes place.
        # If the function doesn't require any arguments:
        #     @CleanExit(handler=func)
        # If the function requires arguments:
        #     @CleanExit('foo', handler=handle)

        self.kw = kw
        self.args = args
        self.handler = kw.get('handler')

    def __call__(self, original_func):
        decorator_self = self
        @wraps(original_func)
        def wrappee(*args, **kwargs):
            try:
                original_func(*args,**kwargs)
            except KeyboardInterrupt:
                if self.handler:
                    self.handler(*self.args)
                else:
                    sys.exit(0)
        return wrappee

def handle():
    answer = raw_input("Are you sure you want to exit?").lower().strip()
    if 'y' in answer:
        sys.exit(0)

@CleanExit(handler=handle)
def f():
    while 1: pass

f()

Upvotes: 0

Views: 68

Answers (1)

Izaak van Dongen
Izaak van Dongen

Reputation: 2545

Your problem is that you're not doing anything to continue the function after handling it - so your code handles the interrupt, and then exits anyway. You can recursively re-enter wrappee if the handler exits successfully like this:

    def __call__(self, original_func):
        decorator_self = self
        @wraps(original_func)
        def wrappee(*args, **kwargs):
            try:
                original_func(*args,**kwargs)
            except KeyboardInterrupt:
                if self.handler:
                    self.handler(*self.args)
                    wrappee(*args, **kwargs)
                else:
                    sys.exit(0)
        return wrappee

Now this should work. Note that this is a little bit naughty, as Python can't optimise tail calls, so if you KeyboardInterrupt more often than sys.getrecursionlimit(), Python will run out of stack frames and crash.

EDIT: That was silly - having thought about it, this function is so trivial to derecurse by hand it probably doesn't even count.

    def __call__(self, original_func):
        decorator_self = self
        @wraps(original_func)
        def wrappee(*args, **kwargs):
            while True:
                try:
                    original_func(*args,**kwargs)
                except KeyboardInterrupt:
                    if self.handler:
                        self.handler(*self.args)
                    else:
                        sys.exit(0)
        return wrappee

should also work just fine.

Upvotes: 2

Related Questions