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
isoris not, not==or!=All return statements should return something explicitly or none should. Include an explicit
return Noneat the end of a function if the function can return somethingUse
startswithandendswithon strings instead of slicing when possibleRely on falsiness of empty sequences in
ifstatements instead of using length zero checksRely on truthiness of booleans in
ifchecks (nox == Trueorx is True)
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