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 functioninput()
orraw_input()
is
waiting for input also raise this exception. The exception inherits
fromBaseException
so as to not be accidentally caught by code that
catchesException
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
How to Dynamically Compose an or Query Filter in Django
How to Activate a Virtualenv Inside Pycharm's Terminal
Problem with Multi Threaded Python App and Socket Connections
How to Check the Versions of Python Modules
Libxml Install Error Using Pip
Get a Function Argument's Default Value
Splitting a List Based on a Delimiter Word
Cannot Install Lxml on MAC Os X 10.9
Cannot Concatenate 'Str' and 'Float' Objects
Usage of Sys.Stdout.Flush() Method
Collision Between Masks in Pygame
How to Find Numeric Columns in Pandas
How to Scroll Frame Using Mouse Wheel & Adding Horizontal Scrollbar