The first item is a little strange (I would consider removing it). The second item is the one I want to address here.
My idea is to move away from the pattern
except:
<do something>
raise
and instead use
finally as e:
if e:
<do something>
which will be added to the language as syntactic sugar for:
finally:
e = sys.exception()
if e:
<do something>
del e
The advantages are:
You don’t accidentally swallow an exception by forgetting the raise.
It is clear that when we use except: we intend to (at least sometimes) handle the exception, and when we use finally we don’t (at least when the linters are silent).
My intention is not to break any existing code or to force anyone to use finally as. It is to make the new pattern available so that we can start promoting it now, and new programmers can learn that bare except: is to be avoided, without caveats.
try:
func()
except ValueError as ex:
...
except KeyError as ex:
...
finally as ex:
# what type is ex?
We separate out the different exception handlers but then sort of always handle the finally as exception as a generic exception with all matches going into it.
It makes me wonder: How common is this pattern to begin with? I can’t think of a recent case where I needed the exception inside the finally block since I used it in the except block.
The reason that PEP 8 and linters advise against using except: is that it catches all exceptions, and any code can raise exceptions that you should treat as critical (in the sense that the program should not ignore them). The usual examples are KeyboardInterrupt and MemoryError, but there are other cases. Simply logging that fact that someone requested to exit the program with a ctrl-C and moving on doesn’t make sense.
Strictly speaking, it’s the exception that is in-flight when the finally block executes. This is any exception that was not caught, caught and reraised, or a new exception raised from an except clause. If finally as e is confusing, maybe finally e (as I originally proposed) works better.
This is proposed as an alternative to
except:
...
raise
If you look for this pattern (say in the stdlib) you will find it used quite a bit.
I can figure it out from the e = sys.exception() example, but I think it should be explicitly stated, what the intention of finally as e is, when no exception is raised in try (e.g. the same as finally: pass?).
In some cases KeyboardInterrupt can be ignored because the app may install a process-wide signal handler through signal.signal, and MemoryError can be ignored because the try block can be in a loop loading a list of large models, failing to load a few of which due to memory error may affect just the list of available models for the end user to choose from and would not affect the proper functions of the rest of the app. In such non-critical sections it may be perfectly okay to simply log any error that occurs and move on.
What I find strange in the PEP 8 wording is “it’s fine if you log it”. As if that makes any difference to whether you should swallow the exception or not. Logs are for post mortem, they are not a part of your program’s behaviour.
Actually, this means that finally as e is not an alternative to except: because it can’t distinguish between an uncaught exception and a raised/reraised exception. That probably kills my idea.
Seems like a preconceived idea about the possible uses of logs is negating reality. If it is something the program does, it is behavior.
This is one preconception. This doesn’t have to be. They are just one type of output. Perhaps not the main one, but the program can be perfectly alive when the information in the logs is accessed and used.
ctrl+C is not necessarily a request to exit the program. It’s a Keyboard Interrupt, and that is something different applications treat differently. Some use it to interrupt ongoing work and go back to the prior menu (tui).
Proper signal and interrupt handling is much more involved than any of the “simple examples” that just label catching keyboard interrupt as incorrect. Those examples might be incorrect from your perspective, but the ability to catch and handle it is not.
For the record, the correct way to tell a program to exit is to send a sigterm, and then wait for the application to do all of it’s cleanup and exit, anything else indicates that you’re not letting a program gracefully shutdown and may lose data, tasks, corrupt state, etc or that the application in question doesn’t handle signals properly. There is no amount of time that it is “correct” to follow that up with a sigkill, sigkill should only be used when you know a program is in a hung state it can’t recover from itself and do not care about the consequences.
You’re right, of course. But going back to the point I was responding to, I think we can agree that proper interrupt handling is unlikely to involve swallowing a KeyboardInterrupt with a bare except (with or without logging).
The user did, after all, ask for something with the Ctrl-C.
It kills the idea of replacing the bare except, but making it more convenient to access the in-flight exception in finally is still a good idea. Particularly if that catches both first-order and higher-order exceptions.
I don’t really agree with this. While I typically write python code that is primarily intended to be run from a console anyway, I think it would be reasonable for a GUI application to just log (presumably visibly in the console) that it received and ignored a keyboard interrupt because the intended way to interact (including to close the application) is the GUI, rather than try and define console behavior for an application not intended to be run from the console.
If the application decides to ignore a KeyboardInterrupt that’s fine. It would probably do this with except KeyboardInterrupt rather than a bare except.
(I don’t see why it makes sense to note this in the log though. Either notify the user via the GUI, or don’t bother.)
logging it is useful because it (usually) shows to the user and they can see the application didnt hang, they’re just using it unexpectedly, and it’s also useful for anyone looking at logs in a post mortem to see if there were things outside of expectations happening.
I think it’s typically better to disable the default handler for signint in this case, and handle this in particular in other ways, but I don’t think it’s unreasonable for some kinds of applications to intend to catch everything at certain places because they are designed with the expectation that closing the application will not be via interrupt promoted to an exception, and want to log and continue if possible rather than crash.
I prefer the current pattern because it explicitly separates types to allow raising only if a certain exception type is being processed.
I keep rereading it and thinking we handle the exception in the except block, why would we complicate finally by shoving it in there when it could be handled in the except block?
To me finally only makes sense when the code runs all the time. It still would here, since you’re protecting it with the if check, but still: why the change if the current pattern works?
I guess the current pattern seems more clear to me.
Of course maybe the new pattern would be more clear with more exposure but I don’t know.
One example – IDLE. It runs two processes – one implements GUI and other executes user code. User code can raise arbitrary exception. It does not stop the process, any exception is sent to the GUI process to display.
Other example – any long-running server. Any error in handling a user request is logged and corresponding error code is sent back to user. It usually does not stop handling other requests.
As for the original proposition, I do not see value of it. It does not allow to express something that is not possible to express now. It does not even make common pattern clearer (it is not so common and is not clearer). It obfuscates common syntax construction – I do not know any other programming language that blurs the border between except and finally. I predict that there will be a lot of confusion between except BaseException as e and finally as e.
What I find odd about PEP 8 is “it’s fine to swallow it if you log it first”. In your example the exception is handled (by the other process). Maybe it’s logged, maybe not, that’s not what makes a difference to whether this is ok or not.
The intention was to draw the border at “are we intending to handle the exception (at least sometimes) or not”. Currently except: is used in both cases. Anyway, I’ve rejected my proposal for another reason.
When I execute a program in my shell I expect Ctrl-C to exit that program unless there’s a really good reason for it not to. Being a GUI program is not a good reason by itself.
Being written in Python stands out as a common denominator for programs that refuse to exit on Ctrl-C in my experience.
Being a GUI program is an excellent reason not to handle KeyboardInterrupts at all or ignore them entirely. not handling them at all comes with all of the issues of killing an app with sigkill, and I don’t want to expose users to that.
I prefer ignoring them entirely, but the very few GUI apps I’m responsible for have actual prompting on exit for whether the user wants to save prior to quitting when there are unsaved changes, and I consider non-responsiveness to actual exit signals to be a critical design flaw. There’s not a reasonable expectation that a GUI app will be run from a terminal, and I’m not going to try and hack together handling for that in an inappropriate manner or add a tui that only shows up for someone trying to run my app in an unsupported way, I’m going to tell the user it’s an unsupported use.