Python Style Part 1 - PEP8

CS106A doe not just teach coding. It has always taught how to write clean code with good style. Your section leader will talk with you about the correctness of code, but also pointers for good style.

Messy code works poorly — hard to read, hard to debug. To help you develop the right habits, we want you to always turn in clean code, and of course all of our examples should also have good clean style.

The Word is Readable

The goal of good style is "readable" code, meaning that when someone sweeps their eye over the code, what that code does shines through.

Experience has shown that readable code is less time consuming to build and debug. Debugging especially can be a huge time-sink, so learning to write clean code and keep it clean is how we will do things all the time. Like surgeons keeping their hands clean, it's the right way to do it and we will try to develop the habit all the time.

Readability 1 - Function and Variable Names

The first step to good readability is a good variable and function names, like this example:

url = get_browser_url()
if has_security_problem(url):
    display_alert('That url looks sketchy!')
    html = download_url(url)

The narrative steps the code takes are apparent from the good function and variable names (essentially the verbs and nouns in the narrative). Notice that no other comments are necessary here. The names alone make it very readable.

The functions are the actions in the code, so we prefer verb names like "display_alert" or "remove_url" that say what the function does. Variables are like the nouns of the code, so we prefer names that reflect what that variable stores like "urls" or "original_image" or "resized_image". We'll talk about naming in more detail in the Style 2 document.

Readability 2 - PEP8 Tactics

Python is developed as a collaborative, free and open source project. A "PEP" (Python Enhancement Proposal) is a written proposal used in Python development.

PEP8 is a consensus set of style and formatting rules for writing "standard" style Python, so your code has the right look for anyone else reading or modifying it. This is very much like English spelling reform, where "public" is the standard spelling, and "publick" is out of use and looks weird.

Here are the most important PEP8 rules we will use. The code you turn in should abide by PEP8.

Indent 4 Spaces

Indent code logically using spaces — 4 spaces is the most common. For if/for/while .. write "body" lines indented on the next line after the colon. Early in the life of Python, some programs used 2 spaces and some used 4 spaces, but 4 spaces has grown to dominate. The best practice is to use literal spaces to indent the code, not tab stops.

if color == 'red':
    color = 'green'

Single Space Between Items

Most lines of code have "operators" mixed in such as as + * / = >=. Use a single space to separate the operators and items:

bound = x * 12 + 13
if x >= 4:
    print(x + 2)

Space After Comma/Colon

A comma or colon has 1 space after it, but no spaces before it.

# e.g.
foo(1, 'hello', 42)  # params to a fn call
[1, 2, 3]            # e.g. list
{'a': 1, 'b': 2}     # e.g. dict

Use a Consistent Quote Mark

Python strings can be written within single quotes like 'Hello' or double quotes like "there". PEP8 requires a program to pick one quote style and use it consistently. For CS106A, we recommend just using single quote. Single quote is tiny bit easier since it does not require the shift key. Just always use single quote and don' think about it.

2 Blank Lines Before Def

PEP8 requires 2 blank lines before each def in a file. This is one of the weaker PEP8 rules. We will not be very upset about your code if it has 3 lines between functions.

Blank Lines Within a Def

If the lines of code have have natural phases - a few lines setting up the file, a few lines sorting the keys, .. use blank lines within the def to separate these phases from each other, much like diving an essay into paragraphs. The standard says to use blank lines "sparingly".

Do Not Write: if x == True

The way that boolean True/False is evaluated in an if/while test is a little more complicated than it appears. The simple rule is this: do not write if x == True or if x == False in an in/while test.

Say we have a print_greeting() function that takes in a "shout_mode" parameter.

Do not write this:

def print_greeting(words, shout_mode):
    if shout_mode == True:   # NO not like this

Write it this way:

def print_greeting(words, shout_mode):
    if shout_mode:           # YES like this

To invert the logic do not use == False. Use not like this:

def print_greeting(words, shout_mode):
    if not shout_mode:
        print('Not shouting')

Remove Pass from Code

The pass directive in Python is a placeholder line that literally does nothing. It is very rarely used in regular production code. However, in coding exercises, there is often some boilerplate code with a pass marking the spot where the student code goes. Remove all the pass markers from your code before turning it in.

# Inline Comments

It's a good practice to add # "inline" comments to explain what especially non-obvious or interesting code does. Inline comments are not needed when the code itself does a good job of showing what the line does.

1. For the following lines, the code does a good job of telling the story, so no inline comments are needed beyond the good variable and function names. Most lines of code are like this.

count += 1
total_len = len(name) + len(address)

2. Inline comments are appropriate as in the example below, explaining what a non-obvious line accomplishes - addressing the bigger goal picture, not the mechanics of the operators.

# increase i to the next multiple of 10
i = 10 * (i // 10 + 1)

See how the inline comment provides useful information that is not obvious from the line itself.

3. Another important use of inline comments to note when the code does not do what it appears to. This speaks to readability - the code does not do what its variable and function names suggest, so we need an inline comment to clear things up.

vendors.add('Stanford University')
# Note: We pass 'T-Mobile' here, but for historical
# reasons the payment system re-writes and uses
# 'Vodafone' for this internally.

Teaching - example code used in teaching often has more inline comments than regular production code, since for teaching there are many times we want to point out or explain a technique shown on a line.

The Misguided PEP8 Rule About ==

How do you compare two values in Python? The answer is: use the == operator like this:

if word == 'meh':
    print('Enthusiasm low')

It works for strings, it works for ints, it works for lists, it works for pretty much everything. Having nailed down that == is the simple, correct thing that should come to mind when comparing two things, you may skip the following paragraph.

There is a IMHO misguided suggestion in PEP8: it requires that comparisons to the special value None use the obscure is operator instead of ==. That is a bad idea, since comparing to None is pretty common, but the is operator is obscure. In fact it's unusual for a program to have any need for the is operator at all. Forcing the mass of regular programs to use this boutique operator most people have never heard of and will never need is not good Python. Therefore, the CS106A position it to just use == to compare any two values, which is the simple, intuitive way to think about comparisons.

if val == None:  # CS106A says this is fine
    print('Is nothing sacred?')

Naming quirks:

Python Keywords

A few words like def, for, and are fixed "keywords" in Python, so you cannot use those words as variable or function names.

>>> import keyword
>>> keyword.kwlist
['False', 'None', 'True', 'and', 'as', 'assert', 'async', 'await', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield']

"len" Rule: Do Not Use Builtin Function as a Variable Name

Python has many built-in functions like len() and range(). As a strange example of Python's flexibility, your code can use assignment = to change what len means within your code .. probably a huge mistake!

>>> len('hello')   # len function works
>>> len = 7        # redefine "len" to be 7, bad idea
>>> len('hello')   # oh noes!
TypeError: 'int' object is not callable

Here is the simple rule: do not use the name of a common function as a variable name. Here are the most common function names:

len abs min max int float list str dict range map filter format

Rather than memorize the list of all the function names to avoid, it is sufficient avoid using the names of functions that your code uses, typically the well known functions like len, range, min, and so on.

This is why we avoid using "str" or "list" as variable names, instead using "s" and "lst".

Redefining a function in this way only affects your local code, not code elsewhere in the program. So if your code accidentally defines a variable named divmod, that will not interfere with other code calling the divmod() function.