Capture Keyboardinterrupt in Python Without Try-Except

Capture keyboardinterrupt in Python without try-except

Yes, you can install an interrupt handler using the module signal, and wait forever using a threading.Event:

import signal
import sys
import time
import threading

def signal_handler(signal, frame):
print('You pressed Ctrl+C!')
sys.exit(0)

signal.signal(signal.SIGINT, signal_handler)
print('Press Ctrl+C')
forever = threading.Event()
forever.wait()

Python: catch Exception or keyboard interrupt

If you want to handle the two cases differently the best way of doing this is to have multiple except blocks:

import time

try:
time.sleep(3)
raise Exception('error')

except KeyboardInterrupt:
print("Keyboard interrupt")

except Exception as e:
print("Exception encountered:", e)

Mind the order!

Avoiding accidentally catching KeyboardInterrupt and SystemExit in Python 2.4

According to the Python documentation, the right way to handle this in Python versions earlier than 2.5 is:

try:
foo()
except (KeyboardInterrupt, SystemExit):
raise
except:
bar()

That's very wordy, but at least it's a solution.

KeyboardInterrupt not raised or caught in the case of broad Exception

You cannot catch KeyboardInterrupt by catching Exception because the former inherits from BaseException only. You can read about this in the docs:

exception KeyboardInterrupt

Raised when the user hits the interrupt key (normally Control-C or Delete). During execution, a check for interrupts is made regularly.
Interrupts typed when a built-in function input() or raw_input() is
waiting for input also raise this exception. The exception inherits
from BaseException
so as to not be accidentally caught by code that
catches Exception and thus prevent the interpreter from exiting. (Emphasis mine)

This means that you would have to do:

except BaseException, e:

But that is considered a bad practice. It would be better to just catch KeyboardInterrupt itself like in your first example.

Ctrl-C ends my script but it is not caught by KeyboardInterrupt exception

I already suggested in my comments to the question, that this problem is likely to be caused by the code section that is left out in the question. However, the exact code should not be relevant, as Python should normally throw a KeyboardInterrupt exception, when Python code gets interrupted by Ctrl-C.

You mentioned in the comments that you use the boilerpipe Python package. This Python package uses JPype to create the language binding to Java... I can reproduce your problem with the following Python program:

from boilerpipe.extract import Extractor
import time

try:
for i in range(10):
time.sleep(1)

except KeyboardInterrupt:
print "Keyboard Interrupt Exception"

If you interrupt this program with Ctrl-C the exception is not thrown. It seems that the program is terminated immediately leaving the Python interpreter with no chance to throw the exception. When the import of boilerpipe is removed, the problem disappears...

A debugging session with gdb indicates that a bulk amount of threads got started by Python if boilerpipe is imported:

gdb --args python boilerpipe_test.py
[...]
(gdb) run
Starting program: /home/fabian/Experimente/pykeyinterrupt/bin/python boilerpipe_test.py
warning: Could not load shared library symbols for linux-vdso.so.1.
Do you need "set solib-search-path" or "set sysroot"?
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/libthread_db.so.1".
[New Thread 0x7fffef62b700 (LWP 3840)]
[New Thread 0x7fffef52a700 (LWP 3841)]
[New Thread 0x7fffef429700 (LWP 3842)]
[New Thread 0x7fffef328700 (LWP 3843)]
[New Thread 0x7fffed99a700 (LWP 3844)]
[New Thread 0x7fffed899700 (LWP 3845)]
[New Thread 0x7fffed798700 (LWP 3846)]
[New Thread 0x7fffed697700 (LWP 3847)]
[New Thread 0x7fffed596700 (LWP 3848)]
[New Thread 0x7fffed495700 (LWP 3849)]
[New Thread 0x7fffed394700 (LWP 3850)]
[New Thread 0x7fffed293700 (LWP 3851)]
[New Thread 0x7fffed192700 (LWP 3852)]

gdb session without the boilerpipe import:

gdb --args python boilerpipe_test.py
[...]
(gdb) r
Starting program: /home/fabian/Experimente/pykeyinterrupt/bin/python boilerpipe_test.py
warning: Could not load shared library symbols for linux-vdso.so.1.
Do you need "set solib-search-path" or "set sysroot"?
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/libthread_db.so.1".
^C
Program received signal SIGINT, Interrupt.
0x00007ffff7529533 in __select_nocancel () from /usr/lib/libc.so.6
(gdb) signal 2
Continuing with signal SIGINT.
Keyboard Interrupt Exception
[Inferior 1 (process 3904) exited normally

So I assume that your Ctrl-C signal gets handled in a different thread or that jpype does other odd things that breaks the handling of Ctrl-C.

EDIT: As a possible workaround you can register a signal handler that catches the SIGINT signal that the process receives when you hit Ctrl-C. The signal handler gets fired even if boilerpipe and JPype are imported. This way you will get notified when the user hits Ctrl-C and you will be able to handle that event at a central point in your program. You can terminate the script if you want to in this handler. If you don't, the script will continue running where it was interrupted once the signal handler function returns. See the example below:

from boilerpipe.extract import Extractor
import time
import signal
import sys

def interuppt_handler(signum, frame):
print "Signal handler!!!"
sys.exit(-2) #Terminate process here as catching the signal removes the close process behaviour of Ctrl-C

signal.signal(signal.SIGINT, interuppt_handler)

try:
for i in range(10):
time.sleep(1)
# your_url = "http://www.zeit.de"
# extractor = Extractor(extractor='ArticleExtractor', url=your_url)
except KeyboardInterrupt:
print "Keyboard Interrupt Exception"

Finish loop before exiting with a KeyboardInterrupt

Thank you to Omer Ben Haim for providing an answer in the comments.

Indeed, the SIGINT signal can be captured using the signal module. Here is some proof-of-concept code that demonstrates this:

import signal
import time

stop = False

def handler(sig, frame):
global stop
stop = True

signal.signal(signal.SIGINT, handler)

############

i = 0
while i<10**6 and not stop:
print("Part 1:", i)
time.sleep(0.5)
print("Part 2:", i)
time.sleep(0.5)
i += 1


Related Topics



Leave a reply



Submit