Hartley Brody

A Quick Primer on Writing Readable Python Code for New Developers

python-style-guideIn general, code is written once in a few minutes (or hours) and then read and maintained for years to come. Ensuring that the code you write today is readable and makes sense to other engineers down the line is a Really Good Thing and you should always keep readability in mind as you’re writing code, even for small one-off scripts.

This is an adaption of an article I wrote on the engineering wiki at Buffer, where I work as a growth hacker.

The Zen of Python

Run import this from a python interpreter to see the official “zen of python”

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!


tl;dr: Whatever IDE or text editor you’re using probably has a plugin that will notify you when the code you’re writing doesn’t match these styles. Installing one was the best decision I ever made as a python programmer. (Besides reading “Dive into Python”)

Ideally, there should be One Correct Way of writing a given statement or expression. If everyone decides their own conventions for spacing, variable naming, etc. then code becomes harder to read and maintain. The PEP8 style guide is the most commonly used convention for addressing this problem in python. Written by Guido van Rossum – the creator of python – it recommends the following conventions:

  • Use 4 spaces for indentation, not tabs
  • A single space after every comma and between every operator:
v1, v2 = 0, 0  # much easier to quickly scan and read
my_dict = {"key1": 1, "key2": 2}
  • Two blank lines between classes and top-level function definitions, one blank line between class methods.
def function():

class MyClass(object):

    def __init__(self, arg):
        super(MyClass, self).__init__()
        self.arg = arg

    def get_arg_offset(self):
        return self.arg * 10

Imports should be on separate lines, and should be explicit:

import sys, time, os
import sys
import time
import os

# this is okay, multiple imports from same package
from os import environ, chdir

# NEVER do wildcard imports. Lots of badness ahead.
from somereallybigpackage import *

# Explicitly import the things you need. Avoids
# collisions and makes later code way more readable.
# Ie: "Where is this function defined? Oh, I see it
# was imported from somereallybigpackage"
from somereallybigpackage import func1, func2, func3, class1, CONSTANT

# If you really need everything, then just
# import the module and use it as a namespace.
import somereallybigpackage

Variables are where you give your code meaning and context. Variable names should err on the side of being long and explicit. It may take a few extra seconds to type, but it saves other engineers hours of trying to figure out what you meant. If your variable names are shorter than five characters, they’re probably too short.

P.S. They say one of the hardest problems in programming is naming things.

# BAD, what do these variables mean?
t = datetime.datetime.strptime(strdate, "%Y%m%d")
ts = int(time.mktime(t.timetuple()))
t2 = t - timedelta(1)
ts2 = int(time.mktime(t2.timetuple()))

# GOOD, very explicit
current_cohort_user_query = build_cohort_query(joined_at__gte=current_cohort_month, joined_at__lt=next_cohort_month)
current_cohort_users = UserMetrics.objects.raw_query(current_cohort_user_query)

Comments can be super useful when there’s a lot of “magic” going on or if you’re worried that there’s a lot of logic that might be hard for a new reader to understand. But beware the out-of-date comment! Whenever you change code, be sure to change any nearby comments if they’re no longer true. Comments that contradict the code are worse than no comments.

def function():
    Any function that's longer than a few lines
    should have a docstring that explains, at minimum:
      1. what params the function takes, if any
      2. what it should be expected to return
    Docstrings should be immediately after the function
    definition and are usually multi-line comments that
    use the triple-quote syntax, not hashtags.
    # If you need to use in-line comments, you can either put them
    # above a big block of code, like this...
    while True:
       counter_var += 1  # or put them inline, with two spaces between the code and the hashtag
    # Note there's always a space between the hashtag and first letter of the comment.

Final Thought

It may seem like splitting hairs, especially if the code still functions regardless of whether it follows the style guide. But having clean, easy-to-read code is a huge investment in your code base and saves you from piling up mountains of technical debt down the line. If any engineer can open a file and quickly figure out exactly what’s happening and make edits, you become a much faster engineering team.