This is not a Heisenbug

Learning curves can be deceitful
JCZD Python INABIAF

This is not a Heisenbug is a short story on how I thought I found a Heisenbug in the Python interpreter while trying to test its limits. This should be self-explanatory once all has bee read.

It’s also interesting to note what attention and reactions a simple question can spark : meet my friends on #python

Input

Consider the following 4 snippets of Python code:

try:
    foo
    print("exit(0)")
    exit(0)
except:
    print(f"exit(1)")
    exit(1)
finally:
    from sys import exit
"""
output:
exit(1)
"""
try:
    foo=1
    print("exit(0)")
    exit(0)
except NameError as e:
    print(f"exit(1): {e}")
    exit(1)
finally:
    from sys import exit
"""
output:
exit(0)
"""
try:
    foo=1
    print("exit(0)")
    exit(0)
except:
    print("exit(1)")
    exit(1)
finally:
    from sys import exit
"""
output:
exit(0)
exit(1)
"""
try:
    foo=1
    print("exit(0)")
    exit(0)
except Exception as e:
    print(f"exit(1): {e}")
    exit(1)
finally:
    from sys import exit
"""
output:
exit(0)
"""

Following discussion on #python

#python had been quite for a while, and it seemed as no-one was paying attention. Then I made a statement.

19:14 < petaflot> yo people :-) I believe I have found a Heisenbug. I was testing to see how the parser reacts (and it IMHO does really well). I also noticed there is a built-in exit() that behaves not quite the same as sys.exit() (this
                  change of behaviour was not tested in the current context) https://bpa.st/2RGQ
19:15 < bjs> petaflot: the built-in exit() comes from the `site` module see https://docs.python.org/3/library/constants.html#constants-added-by-the-site-module
19:15 < SnowJ> petaflot, you shouldn't use the built-in `exit()` in programs, use `sys.exit()` instead
19:15 < SnowJ> (`exit()` isn't guaranteed to be there)
19:18 < petaflot> anyway. bui.lt-in or not built-in (thanks for the warning SnowJ) there is definitely a isenbug: https://bpa.st/JSYA
19:18 < petaflot> WAIT careful in this last paste the output value (in the comment is wrong, I forgot to update it
19:19 < bjs> petaflot: I'm not sure what thing you think here is a "heisenbug" or why
19:19 < SnowJ> petaflot, I don't know what you think the bug is. Maybe it will help you correct your understanding if I say that both exit() and sys.exit() raise exceptions.
19:19 < bjs> petaflot: this code should print exit(0) and nothing else
19:20 < JAA> `exit(0)` raises a `SystemExit`, which derives from `BaseException`, not `Exception`.
19:20 < Festive_Derg> yes but it NameError-s, I'd expect
19:20 < petaflot> so use this: https://bpa.st/ZAHQ
19:20 < SnowJ> petaflot, there is no bug there, for the reason that JAA just described: SystemExit is not an instance of Exception
19:21 < JAA> bjs: And it does.
19:21 < bjs> petaflot: exit() on its own comes from `site`. I guess the problem you are encountering is that it's not *required* that exit() exists at all
19:21 < petaflot> why does https://bpa.st/raw/SQTQ show "exit(0)" and then "exit(1)" ?
19:21 < bjs> petaflot: because `except` catches all exceptions, including those that aren't instances of Exception
19:21 < SnowJ> petaflot, because `except:` catches *all* exceptions and `except Exception:` does not
19:21 < Festive_Derg> oh. TIL that exit() is in site. how did I not know that.
19:21 < bjs> petaflot: this also applies for e.g. KeyboardInterrupt and so on. Note how `except Exception as e:` doesn't catch when you Ctrl-C
19:22 < petaflot> ok, I get it. BaseException.
19:22 < Festive_Derg> or a subclass thereof
19:22 < SnowJ> you almost always should not write a 'bare' `except:` or catch `BaseException`, but there is no bug here.
19:22 < bjs> Festive_Derg: but you are right that it *might* NameError, if exit() wasn't added to built-ins (e.g. because you loaded without `site` with -S)
19:23 < Festive_Derg> bjs: yeah, makes sense, but still funny
19:24 < Festive_Derg> also I cant think of an actual use for catching BaseException; Ive caught SystemExit before but catching BaseException in general sounds like a terrible idea
19:24 < bjs> Festive_Derg: the reason it exists is because of REPLs, things like `python -i` catch all exceptions at the top-level loop (inlcuding Ctrl-C and so on)
19:24 < JAA> It's fine if you do some error handling and then reraise, but yeah, rare.
19:24 < Festive_Derg> okay yeah theres _one_ use
19:24 < bjs> Festive_Derg: so there exists exit() and quit() and other things for use in the REPL, which actually exit by closing stdin
19:24 < Festive_Derg> oh interesting
19:25 < bjs> Festive_Derg: since if stdin is closed, the REPL exits. That's why Ctrl-D closes the REPL, but Ctrl-C doesn't...
19:25 < Festive_Derg> also TIL IPython auto-calls exit if you just type it (was looking at `exit is sys.exit`
19:25 < Festive_Derg> )
19:25 < SnowJ> Festive_Derg, I have never had a reason to catch BaseException and suspect I probably won't have one in the future
19:26 < Festive_Derg> SnowJ: unless as bjs points out you're writing a repl
19:26 < Festive_Derg> but even then you'd need to be very careful
19:26  * SnowJ does not expect to write a REPL
19:26 < bjs> Yeah in 'real' programs if I can call them that, I don't expect many instances where you'd want to catch BaseExceptions
19:26 < JAA> SnowJ: A REPL in terribot would be neat though. :-P
19:26 < Festive_Derg> SystemExit, if you ever want to use argparse as a parser internally, you'll end up catching
19:26 < bjs> some threaded worker queues and some other corner cases perhaps, but usually just to propagate it somewhere else
19:27 < SnowJ> JAA, I did briefly consider it when I wrote the MicroPython plugin :)
19:27 < JAA> :-)
19:27 < Festive_Derg> Ive used it in some bots and IRC client scripts, its cute when /the client closes/ after you run /your_command -h
19:27 < SnowJ> It would be easier with CPython once subinterpreters land
19:27  * Festive_Derg glares at hexchat
19:27 < SnowJ> (and assuming I figure out how CPython's WASI build works)
19:27 < petaflot> is there a name for such as thing? it looks like a Heisenbug because when I try to observe it it seems to disappear, but when I observe it further I realize it's not a bug but a feature (so there _was_ a bug but "in my
                  brain")
19:27 < bjs> petaflot: learning? :)
19:27 < Festive_Derg> petaflot: misunderstanding
19:27 < SnowJ> misunderstanding +1
19:27 < Festive_Derg> (followed by learning!)
19:28  * petaflot <3

And then it got quite again…