Code Style

Zen of Python

Python programmers take code style seriously. We have a name for code which follows accepted Python idioms: “Pythonic”.

We also have a poem consisting of 20 aphorisms, only 19 of which have been written down. The 20th was left for Guido to complete. Maybe it’s “less is more”?

We can see this poem printed out (thanks to PEP 20) by importing the this module:

>>> 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!

PEP 8

Read PEP 8 or PEP8 for Humans

Indentation:

  • Use 4 spaces for indentation

  • Maximum line length of 79 characters

  • Rely on implied line continuation inside parenthesis, brackets, and braces for wrapping long lines. Do not use backslashes to escape ends of lines.

  • When wrapping indentation, the first character of indented content on each line should line up

  • When wrapping over multiple lines, the closing brace/bracket/parenthesis may line up under the first non-whitespace character

Spaces:

  • No preference stated for single vs. double quoted strings

  • Do not put whitespace immediately inside parenthesis, brackets, or braces

  • Do not put whitespace immediately before a comma, semicolon, or colon

  • Do not put whitespace before the opening parenthesis of a function call or the opening brackets that start indexing or slicing

  • Do not put more than one space around an assignment operator (no = alignment)

  • Always assign the following operators with a single space on both sides: - Assignment - Augmented assignment (e.g. +=) - Comparisons (e.g. ==) - Booleans (e.g. and)

  • When operators with multiple priorities are used, consider adding whitespace around the operators with the lowest priority

  • Don’t use spaces around an equal sign used to indicate a keyword argument or default parameter

Naming Conventions:

  • Python modules should have short, all lower-case names, using underscores when it improves readability

  • Class names should normally use UpperCamelCase convention unless the class is used primarily as if it were a function (then the function convention should be used)

  • Function names should be all lowercase with words separated by underscores as needed to improve readability

  • Constants are usually defined at the module level using all capital letters with underscores separating words

Miscellaneous Idioms:

  • Comparisons to singletons (like None) should be done with is or is not, not == or !=

  • All return statements should return something explicitly or none should. Include an explicit return None at the end of a function if the function can return something

  • Use startswith and endswith on strings instead of slicing when possible

  • Rely on falsiness of empty sequences in if statements instead of using length zero checks

  • Rely on truthiness of booleans in if checks (no x == True or x is True)

  • See PEP 257 for docstring conventions

There are several command-line tools for linting your code based on PEP 8 that we’ll talk about when we cover the standard library.

LBYL vs. EAFP

Look Before You Leap (LBYL) is a coding style that encourages explicitly testing pre-conditions before function calls, attribute usage, or item lookups.

Easier to Ask Forgiveness than Permission (EAFP) is a coding style that encourages assuming existence of necessary attributes, keys, or methods and catching exceptions of those assumptions prove false.

In Python, we tend to practice EAFP. We expect code to raise exceptions when our assumptions are false, so that errors do not pass silently.

Tricks

Let’s say we have some code that checks a condition and sets a variable based on that condition:

if name:
    greeting = f"Hello {name}"
else:
    greeting = "Hi"

We can use an inline if-else statement to do this all in one line. This is similar to the ternary operator in other programming languages.

greeting = f"Hello {name}" if name else "Hi"

Let’s say we’re checking whether a value is within a certain range:

if 0 < x and x < 5:
    print("x between 0 and 5")

We can do this in a more Pythonic way by relying on Python’s ability to chain conditional checks:

if 0 < x < 5:
    print("x between 0 and 5")

Be careful with this though. This differs from languages like C, where operators cannot be chained in this way and will end up comparing True or False to your values.

>>> 0 < 0.5 <= 0.8  # chained comparison
True
>>> (0 < 0.5) <= 0.8  # True <= 0.8
False

When assigning multiple variables to the same value, consider using a single assignment statement instead:

>>> a = 0
>>> b = 0
>>> c = 0
>>> a = b = c = 0

Here we have a function that swaps every other value in a given list:

def swap_every_other(original_values):
    """Swap index 1 with index 0, 3 with 2, etc."""
    values = original_values[:]
    for i in range(1, len(values), 2):
        temp = values[i]
        values[i] = values[i - 1]
        values[i - 1] = temp
    return values
>>> swap_every_other([1, 2])
[2, 1]
>>> swap_every_other([1, 2, 3])
[2, 1, 3]
>>> swap_every_other([1, 2, 3, 4])
[2, 1, 4, 3]
>>> swap_every_other([1, 2, 3, 4, 5])
[2, 1, 4, 3, 5]

Instead of swapping variables with a temporary variable, we can use multiple assignment to swap our variables:

def swap_every_other(values):
    """Swap index 1 with index 0, 3 with 2, etc."""
    for i in range(1, len(values), 2):
        values[i], values[i - 1] = values[i - 1], values[i]
    return values

namedtuple inheritance

Let’s say we have an implementation of a Vector as a namedtuple. This allows our users to use this object in very Pythonic ways.

from collections import namedtuple
import math


Vector = namedtuple('Vector', 'x y z')


def get_magnitude(vector):
    x, y, z = vector
    return math.sqrt(x**2 + y**2 + z**2)

It would be nice if we could say v.magnitude instead of get_magnitude(v):

>>> v = Vector(1, 2, 3)
>>> v
Vector(x=1, y=2, z=3)
>>> get_magnitude(v)
3.7416573867739413
>>> v.magnitude
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Vector' object has no attribute 'magnitude'

Our Vector variable is actually a class. We can make a sub-class of this and extend the functionality.

from collections import namedtuple
import math


BaseVector = namedtuple('BaseVector', 'x y z')


class Vector(BaseVector):
    @property
    def magnitude(self):
        return math.sqrt(self.x**2 + self.y**2 + self.z**2)

Now we can ask the vector for its magnitude directly:

>>> v = Vector(1, 2, 3)
>>> v
Vector(x=1, y=2, z=3)
>>> v.magnitude
3.7416573867739413

Notice that the string representation of our class notes that it is type Vector correctly. That’s because namedtuple is friendly to sub-classing.

next

Let’s say we have a function that tells us whether a number is a power of two and we want to find the first power of two in a list of numbers:

>>> import math
>>> def is_power_of_two(n): return (math.log(n) / math.log(2)).is_integer()
>>> numbers = range(1000000, 10000000)

We could use a list comprehension and take the first value from it. On my computer, this takes over 10 seconds to run with the numbers list we made:

>>> [n for n in numbers if is_power_of_two(n)][0]
1048576

A more efficient way to do this would be to make a generator and take just the first value from it. This runs in less than a second on my computer:

>>> next(n for n in numbers if is_power_of_two(n))
1048576