I still don't quite understand your explanation, but from the code:
try:
main_loop()
except KeyboardInterrupt as e:
print('KeyboardInterrupt')
# do some clean up
raise Exit_Main_Loop() # signal the UI to terminate
There is no way that main_loop
could ever see the Exit_Main_Loop()
exception. By the time you get to the KeyboardInterrupt
handle, main_loop
is guaranteed to have already finished (in this case, because of an unhandled KeyboardInterrupt
), so its exception handler is no longer active.
So, what happens is that you raise a new exception that nobody catches. And when an exception gets to the top of your code without being handled, Python handles it automatically by printing a traceback and quitting.
If you want to convert one type of exception into another so main_loop
can handle it, you have to do that somewhere inside the try
block.
You say:
Unfortunately I can't change main_loop to except KeyboardInterrupt as well.
If that's true, there's no real answer to your problem… but I'm not sure there's a problem in the first place, other than the one you created. Just remove the Exit_Main_Loop()
from your code, and isn't it already doing what you wanted? If you're just trying to prevent Python from printing a traceback and exiting, this will take care of it for you.
If there really is a problem—e.g., the main_loop
code has some cleanup code that you need to get executed no matter what, and it's not getting executed because it doesn't handle KeyboardInterrupt
—there are two ways you could work around this.
First, as the signal
docs explain:
The
signal.signal()
function allows to define custom handlers to be executed when a signal is received. A small number of default handlers are installed: …SIGINT
is translated into aKeyboardInterrupt
exception.
So, all you have to do is replace the default handler with a different one:
def handle_sigint(signum, frame):
raise ExitMainLoop()
signal.signal(signal.SIGINT, handle_sigint)
Just do this before you start main_loop
, and you should be fine. Keep in mind that there are some limitations with threaded programs, and with Windows, but if none of those limitations apply, you're golden; a ctrl-C will trigger an ExitMainLoop
exception instead of a KeyboardInterrupt
, so the main loop will handle it. (You may want to also add an except ExitMainLoop:
block in your wrapper code, in case there's an exception outside of main_loop
. However, you could easily write a contextmanager
that sets and restores the signal around the call to main_loop
, so there isn't any outside code that could possibly raise it.)
Alternatively, even if you can't edit the main_loop
source code, you can always monkeypatch it at runtime. Without knowing what the code looks like, it's impossible to explain exactly how to do this, but there's almost always a way to do it.