Image of 7 Key Takeaways from the Zen of Python

ADVERTISEMENT

Introduction

Every industry has its "best practices" - a set of guidelines for both newcomers and veterans to follow in order to maximize their contribution to the field. Programming is no different, and for Pythonistas, these best practices are codified into what's known as the Zen of Python.

The Zen of Python is a list of 20 rules of thumb for writing Pythonic code (that is, code that exhibits the design philosophy that the creators had in mind when developing the language). 19 of these rules were submitted to a Python Usenet group back in the late 1990s by none other than Tim Peters, one of Python's core developers.

For years, Python programmers have used the Zen to scaffold their own development process. It's become a well-known standard for writing clean and elegant Python code, so much so that it was standardized as PEP 20 in 2004. A PEP is a Python Enhancement Proposal.

In this article, you'll take a look at the different rules of thumb posited by the Zen of Python, as well as how to implement them in your development workflow.

Overview

Barry Warsaw added the Zen of Python into the Python language as an easter egg back in 2001. Thanks to his contribution, anyone can view the Zen straight from the Python interpreter by typing import this:

>>> import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

You may notice that some of these guidelines appear to contradict each other, whereas others may or may not be applicable based on a given scenario.

Also note that, even though the Zen supposedly contains 20 best practices, there are only 19 in the list. In fact, the 20th guideline has never officially been written down, and is said to be “some bizarre Tim Peters in-joke” by Guido van Rossum (the creator of the Python).

What this means is that it's important to maintain a sense of humor when evaluating the Zen of Python. Tim Peters himself has noted that it was originally meant to be nothing more than "a throwaway python-list post." As such, the Zen is not so much a list of hard-and-fast rules that must be followed, but instead a set of general principles by which to guide your development process.

Let's take a closer look at each of these design principles, grouped into 7 major themes that form the basis for writing Pythonic code.

1) Your code is readable

> Beautiful is better than ugly.

> Sparse is better than dense.

> Readability counts.

You may have heard the saying that "beauty is in the eye of the beholder," but when it comes to STEM fields like physics, mathematics, and computer science, the concept of what is "beautiful" takes on a slightly different meaning.

In these fields, beauty is more akin to elegance and simplicity, and these are the attributes that Guido had in mind when developing the Python language. Python is known for being as easy to read and write as the English language. As such, Python developers looking to follow the Zen should ensure their code is consistent, easy to understand, and - where applicable - aesthetically pleasing.

Take the following code block for instance:

for i in range(7):
    s = S(); [s.add(self.d.take(False)) for j in range(i+1)]

Using terse variables and list comprehensions like this can fit a lot of functionality into just a few lines of code, but it will probably be a pain for your colleagues to get up to speed on what your code does. It may even be problematic for your future self to maintain!

Instead, spreading your code out over multiple lines, using verbose names for variables, and adding in descriptive comments can help increase the readability of your code:

# For each of the seven stacks...
for stack in range(7):
	this_stack = Stack()

	# ...take the first card from the Deck without flipping it and add
	# it to the current stack by inserting it into the first position.
	[
		this_stack.add_card(self.deck.take_first_card(flip=False))
		for num_cards in range(stack + 1)
	]

Now, the code is spread out over multiple lines, using verbose variable names and including comments to shed light on functionality at each step.

When following the Zen of Python, one should aim to write beautiful, sparse, and readable code in order to ensure that those who are reading your code (be it your teammates or your future self) can get up to speed and make contributions as quickly as possible.

Your code is appropriate for the use case in question

> Simple is better than complex.

> Complex is better than complicated.

These two guidelines are best understood with clear definitions of the words "complex" and "complicated." According to Webster's 1913 dictionary, something complex is composed of multiple simple parts working together in an intricate fashion. Something that is complicated, on the other hand, may have many simple parts combined in a way that is difficult to understand or hard to work with.

The dividing line between a "complex" solution and "complicated" one is the amount of confusion involved. In most cases, the simplest solution will win out. However, if the problem you're trying to solve calls for multiple interconnected parts, then forcing a simple solution may only invite confusion!

These guidelines are directly connected to object-oriented programming. Early on in your Python journey, you'll learn how to create programs that run in the Python interpreter, or that run as single files from the command line. However, as your skills grow and you start to develop more complex applications, you begin to notice the limits of procedural programming. Trying to keep everything in a single file can cause your code to quickly grow to an unmaintainable size.

Though splitting up your code into separate modules that work together can be a more complex process, in the end it will be less complicated because each module will be easier to debug, test, and maintain on its own. The Zen shows that where complexity is necessary, it should be preferred over simplicity in order to avoid confusion.

Your code follows best practices where necessary

> Special cases aren't special enough to break the rules.

> Although practicality beats purity.

> Flat is better than nested.

> Namespaces are one honking great idea -- let's do more of those!

Python's creators took design and functionality seriously when developing the language. As such, Python programmers will often notice some intricacies that don't apply to other programming languages. For example, take the following code block:

>>> difference = 1.1 - 0.9
>>> difference == 0.2
False

The values 1.1 and 0.9 are subtracted from one another and stored in the variable difference. Then, this value is compared to 0.2. In other programming languages - for instance, Java - this comparison would return True. However, Python won't break the rules of floating-point numbers for primitive data types, so its comparison returns False instead.

In some instances, this consistency makes Python one of the easiest programming languages to work with. On the other hand, it can introduce problems when you truly need to consider those special cases. For instance, a result like the above can introduce errors when you're working with financial data. Indeed, the official documentation even makes note of some of the issues Python developers might encounter with regard to floating point arithmetic. If you were developing an accounting application, for instance, the practical benefits of forcing an equality may outweigh the best practices surrounding Python's objects:

>>> round(difference, 2) == 0.2
True

That being said, following Python's design principles where possible will generally lead to code that is cleaner and easier to read and maintain.

Namespaces are a similar frontier where best practices should be followed to ensure the most Pythonic code. One oft-used illustration is the nesting of one or many modules inside the other:

import foo.bar.baz.qux.quux

In this case, the programmer appears to be implementing the best practice of using namespaces in order to prevent name collisions in their code. Unfortunately, their implementation could be seen as introducing complicated hierarchy into the codebase. Moving the code into a top-level module or class can help mitigate name collisions while inviting a flat structure to aid in readability.

Your code handles errors gracefully

> Explicit is better than implicit.

> In the face of ambiguity, refuse the temptation to guess.

> Errors should never pass silently.

> Unless explicitly silenced.

Error handling is a skill that should be acquired as soon as possible. When writing code, it's inevitable that bugs will be introduced, and you'll have to figure out how to remove them from your program. Inserting the proper error handling techniques ahead of time can assist in the debugging process.

For instance, take a look at the unhelpful except clause below:

def weather():
    try:
        import sunshine
    except:
        print("uh-oh!")

If you had a block of code like this in your program, then when you tried to run it without importing the sunshine module, the only message you would get would be the following:

>>> weather()
uh oh!

Not very helpful, is it? However, by explicitly raising the appropriate error, you can give yourself a starting point for fixing any bugs:

def weather():
    try:
        import sunshine
    except ImportError as e:
        print(e)

Now, the message you'll get instead is this:

>>> weather()
No module named 'sunshine'

Here, it's clear that there's an import error, and now you'll know how to go about fixing it.

While this is a rather contrived example, the principles behind it hold true. Ambiguous code can lead to hours if not days of sitting on a call with a co-worker, combing through the codebase line by line, trying to figure out just where things are going wrong. Explicit error handling helps avoid ambiguity in your code. When something goes wrong, you don't have to guess as to what it might be. Instead, you'll be given a concrete hint as to where you should turn next to start towards a fix.

That being said, there are places where one might want to silence errors. This is often the case when you're working on a data analysis project in a Jupyter Notebook. These notebooks can often raise warnings that try to alert the developer of a potential underlying issue:

Pandas warning

However, these error messages are often not serious enough to require immediate attention. In that case, you might choose to explicitly ignore these warnings when running your code:

import warnings
warnings.filterwarnings('ignore')

Here, you explicitly note at the top of the notebook file that you don't want any warnings to be raised. The Zen just says to make sure that when you're suppressing errors and warnings, you're doing so explicitly. In other words, the choices that you make in your code should be deliberate.

Your code is concise

> There should be one-- and preferably only one --obvious way to do it.

> Although that way may not be obvious at first unless you're Dutch.

In actuality, both of these guidelines are more humorous takes on the way different programming languages are designed than they are true best practices. Indeed, the first is likely a tongue-in-cheek retort to Perl creator Larry Wall's exhortation that "There's more than one way to do it!"

And yet, even the Dutch creator of Python himself couldn't escape the inevitability that there just might be more than one way to do something in Python. Take string formatting, for instance:

>>> # Using the % operator
>>> proglang = "Python"
>>> 'The Zen of %s' % proglang
'The Zen of Python'
>>>
>>> # Using the .format() method
>>> my_str = 'The Zen of {}'
>>> my_str.format(proglang)
'The Zen of Python'
>>>
>>> # Using f-strings
>>> f"The Zen of {proglang}"
'The Zen of Python'
>>>

The above code block shows three different ways that one can format strings in Python. Here, there isn't one and only one way to do string formatting in Python, and the choice of implementation is left up to the developer.

This pair of guidelines tries to impart the trade-off between flexibility and standardization. When there are multiple ways to do things in any given programming language, then one needs to be familiar with all of the different methods in order to be most effective. However, having multiple implementations for the same functionality can introduce a layer of flexibility into your codebase that may be appealing to some developers.

No matter which implementation you decide on, however, try to stick with the same choice throughout your code base. If you're going to use f-strings in one module, then be consistent and use it throughout the rest of your code base as well, choosing "one" way to do things.

You’re shipping (clean code) to production

> Now is better than never.

> Although never is often better than right now.

What good is your code if it never sees the light of day? While some may say that writing code in and of itself is a useful endeavor, the end goal of most development projects is to create something that's functional for the end user. With this goal in mind, the Zen of Python suggests working towards creating something that you can release now.

Many developer teams follow an iterative process, where small improvements in the codebase are released incrementally (often on what's called "nightly builds") instead of waiting for one giant update that changes everything. This means that bugs are discovered much more quickly, and end users can immediately take advantage of new functionality.

In general, this Zen guideline says that it's best to go ahead and try your hand at creating something now than it is to simply sit around and speculate about whether or not it might be a good idea. That being said, the subsequent guideline says that it's better to avoid a shoddy implementation that it is to rush ahead with a concept that hasn't been well thought out.

These are some of the best practices that should be considered in the context of the particular project you have in mind. Are you spending too much time thinking about how the implementation might work, instead of actually trying it out? Or are you in danger of rushing through new functionality that's likely to cause more problems for your users down the line?

Perhaps the next section can help you answer these questions!

You can easily explain your code to non-technical peers

> If the implementation is hard to explain, it's a bad idea.

> If the implementation is easy to explain, it may be a good idea.

As a developer, you'll frequently be in contact with those who are not familiar with programming and have no interest in learning anything technical. And yet, these individuals are responsible for making key decisions surrounding your codebase. Project managers, investors, end users, and others may have a say in which aspects of your code are introduced to the final product, and which parts get scrapped or sent back to the drawing board.

It's absolutely vital that you're able to explain, in plain English, what your code does to people who don't code. If your explanation requires you to define technical terms beforehand or give an aside on CPython internals, then you might want to rethink your implementation.

Note that this guideline applies to your fellow Pythonistas as well, however. Although they may understand what generative adversarial networks are or how to implement concurrency with asyncio, it's still regarded as a best practice to make sure that the idea you have in mind can be explained quickly and succinctly.

If your idea can be easily explained to both developers and non-developers alike, then you just might have a good idea on your hands, and it's time to get coding!

Summary

In this article, you took a look at the Zen of Python, which offers practical guidelines and coding best practices for developers using the Python programming language. More generally, you saw seven key takeaways that one can glean from these guidelines to better inform any development workflow.

Remember, the Zen should be taken with a grain of salt! Keep your sense of humor in mind as you review these Pythonic principles, and incorporate them into your workflow as you see fit.

Next Steps

In this article, you saw three different ways for formatting strings in Python. There are other operations you can perform on strings as well, including making strings lowercase and checking if a string contains a substring.

You also touched on error handling and raising explicit exceptions. Get some practice interpreting common Python errors by looking into the "return outside function" error or the one that reads "'python' is not recognized as an internal or external command".

If you're interested in learning more about the basics of Python, coding, and software development, check out our Coding Essentials Guidebook for Developers, where we cover the essential languages, concepts, and tools that you'll need to become a professional developer.

Thanks and happy coding! We hope you enjoyed this article. If you have any questions or comments, feel free to reach out to jacob@initialcommit.io.