A recent blog post “If you don’t like exceptions, you don’t like Python” has made rounds lately, and compelled me to write a partial rebuttal. It is not like that blog post is completely wrong, but it is not the be-all and end-all of this topic. And if I may add, it is kind of opinionated.
The original article states that exceptions are central to Python, that the common advice “exceptions should only be for errors, not for normal flow control” is wrong, goes on, explaining that exceptions are used in core implementations, such as the iterator protocol, and attribute access, thus that they are a central feature of the language. Some longer parts of the blog posts are concerned debunking commonly held misconceptions by Java and C++ programmers.
Roughly speaking, exceptions in this article are portrait very favourably, with all that praise, all criticism and questions regarding their use are eclipsed.
Use exceptions for error handling
This is a point where I just whole-heartedly agree with barnert. Errors should be propagated using exceptions, so
is a perfectly fine usage of exceptions, and callers should check for these exceptions if their code does not guarantee that the argument is a list of length above 0.
Exceptions are dissociated from values and variables
Sometimes I stumble over code that uses a pattern like this:
This snippet has many exception-related issues and shows how not to use
exceptions. First of all, it is unclear which key-access in the try
block does raise the exception. It could be in
bar[key], or in
_[anotherkey], then in
res[evenanotherkey], or finally it could
dosomething(foo). The exception mechanism dissociates error
handling from the values and variables. My question is: can you tell
whether catching KeyErrors from
dosomething() is intended?
So when using exceptions, one has to be really careful about which
exceptions are caught and which aren’t. With defensive programming (i.e.
haskey())-style checks, it is unambiguous and hardly as “intrusive” to
the code as writing out individual
try-catch blocks for each indexing
So there are basically two risks when using exceptions:
- An exception that should be caught is not caught
- An exception is caught wrongfully
The first risk is definitely a risk, but one that I don’t worry too much
about. The second is a risk I definitely fear. How many functions in
your code can throw
RuntimeError can your code throw?
Exceptions as Pythonic gotos
Exceptions can emulate goto statements. Of course they are jumps to upper levels on the stack, but also within statements. In C code, goto’s are a primary means of function-local control flow and error handling (and for error-handling, they are rather uncontroversial):
You can model this usage with exceptions in Python. I have seen such code in the wild.
In most cases there are ways to avoid this pattern that are preferrable.
Python’s for loops have an optional
else branch that helps avoiding
such jumps. Nevertheless, this pattern can go awry with a
happending at some other place in the loop, etc.
Meta: Ingroup, Outgroup Thinking
What I disklike the most about barnert’s article is probably mostly what one can read in the title: “If …, you don’t like Python”. It is in line with a lot of talk I hear about code/software/solutions being “Pythonic”. What this seems to imply is, that must take sides: Either you are in line with an orthodox Python community, or you are an outsider, someone who is not “Pythonic” enough. All of this is not helpful for improving code.
Exceptions are a central and powerful tool in Python. But use them with care and caution. Do not pretend that they are like a magic wand, don’t use them to show your love for python. Use them when the individual situation calls for exception usage.