Python is a wonderful programming language. It is far and away my high level language of choice, and useful for all kinds of endeavors. Its “batteries included” style makes it a remarkably flexible tool which can be used to build any kind of application you can think of, and makes it highly accessible to newcomers. For veterans whose applications have performance critical components, CPython has a low level C API which allows you to wrap pure C and introduce it into the execution of your Python code. Hell, Python even has a Java implementation for Python developers who want their applications to run on JVMs.
As if that wasn’t cool enough, you can easily push out a graphical interface for your Python application, since there are language bindings for the wxWidgets, QT, and GTK widget kits… just to name a few. Or if you want your Python application’s interface to be web-based, then the WSGI standards and compliant servers can get you there.
Writing for the web is where I have been spending most of my time lately, and it truthfully makes me wonder why anyone would write for the web using anything else. I admittedly haven’t yet ventured down the Ruby/Rails path, but I’ve gone on record before as calling PHP the web development language for chumps, and the more time I spend with Python, the less I regret my inflammatory choice of insult.
However, as delighted as I have been with my Python experience thus far, it has definitely not been without its share of annoyances. Most of them are trivial, but some of them, such as the one I’m about to sound off about, genuinely make me want to rant. So that is exactly what I intend to do.
As with most programming languages I complain about, my beef with Python doesn’t so much have to do with any intrinsic features of the language itself, but instead with prevailing practices exercised by developers using the language. For example, the reason I publicly, shamelessly, and repeatedly disparage PHP is not because it’s some kind of capability-lacking, mickey-mouse programming language. In fact, some of my favorite applications for the web are written in PHP (see OwnCloud). Rather, I disparage it because it seems to be the language of choice for capability-lacking, mickey-mouse developers who wield it to give birth to disgusting, illegible, headache-inducing, semantic atrocities they have the audacity to call code.
Thankfully, the world of Python seems to be slightly less overrun with these infuriatingly clueless nincompoops. Instead, Python is ruled by developers who have a tireless determination to carry the torch of an entirely different and entirely aggravating convention of their own: rampant and unrestrained usage of exception raising.
Wtf is an exception?
For those of you who are new to (or entirely unfamiliar with) high level programming, an exception basically represents the occurrence of a critical problem which prevents the application from executing any further if it is not dealt with by the programmer.
For example, suppose I have a list which stores the names of types of fruit
If I want to recall any of these items individually, I just specify the index of the item I want to recall, where 0 is the first index in the list
1 2 3 4 5 6
So if index 2 is the last item in the list, what happens if I try to select index 3?
1 2 3 4
This raises an exception, which in this case will cause the program to cease execution and immediately exit. If the program were allowed to continue executing in this example, it would either crash due to a segfault, or worse yet, read data from a piece of memory not belonging to the program. Halting execution and immediately exiting is a safer and more graceful way to handle this condition.
There are several conditions under which Python will automatically raise an exception (such as the previous example), but it is possible for the developer to explicitly raise an exception in response to a certain condition. The code block below provides a quick example of how a developer might do this.
1 2 3 4 5 6 7 8 9 10 11 12
If you didn’t follow that snippet, don’t worry about it. Here’s the general idea.
- The function
dict_or_die()was written to expect an input of the type
- If it does not receive an input of type
dict, as the programmer I made a decision to raise an exception, halting the execution of the program.
This is what I would consider an example of good exception raising, as accepting an incorrect input type could actually interfere with the successful execution of this frame, or lead to vulnerabilities in the code.
So… not seeing the problem here.
That’s because exceptions aren’t a problem, when employed properly. The problem is that in my experiences with Python (particularly for the web), framework and library developers don’t seem to share this philosophical paradigm. In fact, beyond not sharing this philosophical paradigm, Python web developers seem to fall back on exception raising as one of their primary methods of error indication.
Why is that a big deal, you ask? Programs should stop and bail out if there’s a problem, you say.
Surely, there is legitimacy to that claim. However, not every error condition is worth halting program execution over. In fact, really only a very few are. That’s why these conditions are called exceptions and not standard errors.
If you’ve ever written and tested code in a low level, compiled programming language (NATIVE compiled code… not that Java/MSIL compiles-to-bytecode bullshit), you know that an error pretty much has one of three possible outcomes.
- A compiler error is thrown, and the program refuses to compile
- A segmentation fault occurs, and the program crashes
- The program keeps running, but does all kinds of wacky shit instead of what you wanted it to do.
In the case of the first outcome, the compiler will give you a useful error message which will more often than not point you directly to the problem. However, the vast majority of bugs you introduce into your program will result in the second or third outcomes. In the case of those outcomes, there are no error messages, no friendly hints, no stack trace. Only either a complete crashing and burning of the program, or a confusing result which is not at all what you wanted. In these kinds of unforgiving conditions, developers are very quick to learn that their code had better be damn diligent about checking for and handling error conditions before it becomes necessary to halt execution if they want it to be at all usable.
What the hell does that have to do with Python for the web, you ask? Hurry up and get to the point, you say. Very well then, insistent reader, allow me to do exactly that. Big boy (and girl) programmers know how to write error handling code. I don’t need you to hold my fucking hand, killing my program if you are for some reason unable to do what I asked you to, the way I asked you to do it. So what? I asked your Loader class to load some object that doesn’t exist. Maybe do something like… return a
None type where you would have otherwise returned the object I asked for, and let me figure out the rest. Don’t kill my entire fucking program and leave my user with an ugly Internal Server Error where they were expecting a web page.
I have watched Python developers try to defend this practice. The most common defense I see can be broken down to two main points
- errors must be made explicit.
- The only way to guarantee an error is made explicit is by raising an exception as soon as it happens
The first point carries a certain amount of validity. An unhandled error can cause unforeseen problems later on in program execution. By that point, the problem can be incredibly difficult to trace back to the source. However, while the problem is valid, the use of rampant exception raising as a solution is complete insanity. Unless the error condition is actually a hinderance to the continued successful and secure execution of the program, then the developer should not be concerned with raising an exception. Instead, the proper approach would be to return some indication of the occurrence of an error to the client code, and allow the client code to handle the error.
Maybe you should just handle exceptions instead of crying about them
Languages which include an exception construct will also include constructs to catch and handle those exceptions before they cause the program to die. Python provides this construct by way of the
try: except block. A quick example demonstrates how this block can be used to prevent a program from dying when an exception is raised.
1 2 3 4 5 6 7 8 9 10 11
This of course begets the reaction, “Well shoot, if that’s all it takes, what in the hell are you fussing about? Just handle the exceptions!”
Sure, I could just handle all the exceptions, and in fact do exactly that (since there really is no alternative when using library code). However, going back to the idea that an exception is supposed to be reserved for the most critical of error conditions, it’s important to realize that the language was designed with that in paradigm mind. Consequently, catching and handling exceptions is a costly operation, which is also why peppering your code with
try: except blocks is considered poor Pythonic practice. In addition to this, being forced to catch and handle frivolous exceptions is a tedious programming task which tends to bog down the development process. To this end, what was intended to help the programmer by making errors explicit becomes a hinderance by slowing down the pace of development
At the end of the day, it doesn’t matter whether the exceptions are handled or not. Superfluous exception raising is a poor programming practice which makes the lives of developers *more difficult, not less so, and results in sub-optimal performance of client code.
Got it all out?
Yep, and I feel a whole lot better for it. Like I mentioned in opening, Python is a great language which has given birth to many wonderful projects, so don’t let my ranting turn you away from it. Just beware of other Python developers and their sometimes nagging insistence to disrupt the execution of your program, and make sure you pack plenty of ibuprophen for the occasion.