I'm trying to raise an exception within an except:
block but the interpreter tries to be helpful and prints stack traces 'by force'. Is it possible to avoid this?
A little bit of background information:
I'm toying with urwid, a TUI library for python. The user interface is started by calling urwid.MainLoop.run()
and ended by raising urwid.ExitMainLoop()
. So far this works fine but what happens when another exception is raised? E.g. when I'm catching KeyboardInterrupt
(the urwid MainLoop does not), I do some cleanup and want to end the user interface - by raising the appropriate exception. But this results in a screen full of stack traces.
Some little research showed python3 remembers chained exceptions and one can explicitly raise an exception with a 'cause': raise B() from A()
. I learned a few ways to change or append data regarding the raised exceptions but I found no way to 'disable' this feature. I'd like to avoid the printing of stack traces and lines like The above exception was the direct cause of...
and just raise the interface-ending exception within an except:
block like I would outside of one.
Is this possible or am I doing something fundamentally wrong?
Edit: Here's an example resembling my current architecture, resulting in the same problem:
#!/usr/bin/env python3
import time
class Exit_Main_Loop(Exception):
pass
# UI main loop
def main_loop():
try:
while True:
time.sleep(0.1)
except Exit_Main_Loop as e:
print('Exit_Main_Loop')
# do some UI-related clean up
# my main script
try:
main_loop()
except KeyboardInterrupt as e:
print('KeyboardInterrupt')
# do some clean up
raise Exit_Main_Loop() # signal the UI to terminate
Unfortunately I can't change main_loop
to except KeyboardInterrupt
as well. Is there a pattern to solve this?
I still don't quite understand your explanation, but from the code:
There is no way that
main_loop
could ever see theExit_Main_Loop()
exception. By the time you get to theKeyboardInterrupt
handle,main_loop
is guaranteed to have already finished (in this case, because of an unhandledKeyboardInterrupt
), 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 thetry
block.You say:
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 handleKeyboardInterrupt
—there are two ways you could work around this.First, as the
signal
docs explain:So, all you have to do is replace the default handler with a different one:
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 anExitMainLoop
exception instead of aKeyboardInterrupt
, so the main loop will handle it. (You may want to also add anexcept ExitMainLoop:
block in your wrapper code, in case there's an exception outside ofmain_loop
. However, you could easily write acontextmanager
that sets and restores the signal around the call tomain_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.