5

I do a lot of Project Euler coding problems, and Python is my go-to language. A lot of programs typically take forever to complete, so I'm working on implementing something that'll help give diagnostic info on the state of the program: when a KeyboardInterrupt occurs I want to be able print how long the program's been running and some info to help me figure out how long it might still take.

The issue with this is--catching the KeyboardInterrupt when you hit Ctrl-C is exiting the program for me still...and I think it has a lot to do with either the structure of this code, or hopefully something in Python I haven't found yet.

I want my code to resume at the same line right after KeyboardInterrupt is caught.

Here's an example of what this code might look like:

     try:
         ...
         ...
         ... #I interrupt code here and hopefully keep going!
         ...
         ...
     except KeyboardInterrupt:
         ...
     finally:
         ...

I hope someone understands the purpose of doing this, and can help me find a way to do this, or to work around this ugly way of calling a break from the running code.

  • 1
    I think what you are looking for is a signal handler. See [this](http://stackoverflow.com/a/1112350/2108173) answer. Of course this means you lose the ability to stop your program using ctrl-c. – Shakkhar Jun 28 '16 at 04:35
  • Great alternative! Thanks for pointing me towards it @Shakkhar –  Jun 28 '16 at 06:12
  • BTW, if your `programs typically take forever to complete`, you may want to read this PyCon lecture on what possibilities you have to alleviate the situation: https://drive.google.com/open?id=0Bw5McUt95YdeMlNiX2VSR1lFRHM – boardrider Jun 28 '16 at 10:50

2 Answers2

5

In general you can never return execution to the specific code segment after an exception was triggered within a try block, given that the exception could occur deep somewhere with a number of other states that are influenced by other side effects in other threads. While this is likely not the case with your program, given that there is no general solution to permit this in Python, what you want to do is basically impossible using exception handling.

However, the default handler for the SIGINT signal is to throw the KeyboardInterrupt exception - if you can hijack that to do something else, you can practically achieve this. Here is a simple program:

import signal
import pdb

def signal_handler(signal, frame):
    pdb.set_trace()

signal.signal(signal.SIGINT, signal_handler)

count = 0
while True:
    count += 1

The SIGINT handler is now simply a function that calls the debugger at the current frame, and with that whenever Ctrl-C is pressed, the debugger is fired at the exact point within the frame that the code is at. You can of course simply go up and inspect the values like so:

$ python /tmp/script.py 
^C--Return--
> /tmp/script.py(5)signal_handler()->None
-> pdb.set_trace()
(Pdb) u
> /tmp/script.py(10)<module>()
-> while True:
(Pdb) pp count
13321869
(Pdb) c
^C--Return--
> /tmp/script.py(5)signal_handler()->None
-> pdb.set_trace()
(Pdb) quit
Traceback (most recent call last):
...
    if self.quitting: raise BdbQuit
bdb.BdbQuit

So the debugger got fired after ctrl-c was done, I then step up to the frame where the loop was running (in your case, your code), then continued execution, killed it again and triggered a bad quit (by typing quit) which terminates the program. If you incorporate this you can basically break at any time and inspect the values of your program anywhere.

metatoaster
  • 17,419
  • 5
  • 55
  • 66
  • you beat me to the answer, but I spent a super long time looking for my quote so I posted my answer anyway :) – en_Knight Jun 28 '16 at 04:42
  • Oh, so by using `Ctrl-C` as a trigger for debug, it has the same intended effect. That's certainly something I wish I had considered. Kick out, check the status, and keep going. @metatoaster would you say it's possible to rig it for any custom output too? –  Jun 28 '16 at 06:09
  • What do you mean by custom input? If you wish to change any values assigned to any given variable at any given frame you can just do it through the debugger. – metatoaster Jun 28 '16 at 08:23
1

“Yeah,” said Harry. “In . . . in a minute. I’ll just clear this up.” He indicated the smashed bowl on the floor. Ron nodded and left. “ Reparo, ” Harry muttered, pointing his wand at the broken pieces They flew back together, good as new, but there was no re- turning the murtlap essence to the bowl. - Harry Potter and the order of the Pheonix

As soon as the keyboard interrupt is raised, that information is lost - the try/catch allows the program to recover, but what was going on before then is lost forever [1].

Luckily, the keyboard-interrupt is actually a signal at the c-level - you can intercept and handle it separately, if you'd like to. The traceback and introspect modules should be helpful

For simple hacks, that should be fine, though I'd be wary of using it in general, based on this comment from the signals docs (implementation dependent):

We still have the problem that in some implementations signals generated by the keyboard (e.g. SIGINT) are delivered to all threads (e.g. SGI), while in others (e.g. Solaris) such signals are delivered to one random thread (an intermediate possibility would be to deliver it to the main thread -- POSIX?). For now, we have a working implementation that works in all three cases -- the handler ignores signals if getpid() isn't the same as in the main thread. XXX This is a hack.


Footnotes

[1] Okay, no it isn't. You can use enough introspection to do anything in python, but it's a terrible idea and doesn't fit with my quote. These people have done it for you if you really want to do something terrible: https://github.com/ajalt/fuckitpy

Community
  • 1
  • 1
en_Knight
  • 5,301
  • 2
  • 26
  • 46