Code Style#

In this chapter, we’ll discuss a number of important considerations to make when styling your code. If you think of writing code like writing an essay, considering code style improves your code the same way editing an essay improves your essay. Often, considering code style is referred to as making our code pythonic, meaning that it adheres to the foundational principles of the Python programming language.

Learning how to consider and improve your code style up front has a number of benefits. First, your code will be more user-friendly for anyone reading your code. This includes you, who will come back to and edit your code over time. Second, while considering code style and being pythonic is a bit more work up-front on developers (the people writing the code), it pays off on the long run by making your code easier to maintai. Third, by learning this now, early on in your Python journey, you avoid falling into bad habits. It’s much easier to learn something and implement it than it is to unlearn bad habits.

Note that what we’re discussing here will not affect the functionality of your code. Unlike programmatic errors (i.e. errors and exceptions that require debugging for your code to execute properly), stylistic errors do not affect the functionality of your code. However, stylistic errors are considered bad style and are to be avoided, as they make your code harder to understand.

Style Guides#

Programming lanugages often have style guides, which include a set of conventions for how to write good code. While many of the concepts we’ll cover here are applicable for other programming languages (i.e. being consistent), some of the specifics (i.e. variable naming conventions) are more specific to programming in Python.

Coding style refers to a set of conventions for how to write good code.

The Zen of Python#

To explain the programming philosophy in Python, we’ll first introduce what’s known as The Zen of Python, which lays out the design principles of the individuals who developed the Python programming language. The Zen of Python is included as an easter egg in Python, so if you import this you’re able to read its contents:

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!

While we won’t discuss each of these above we’ll highlight two of these tenantsthat are particularly pertinent to the considerations in this chapter. Specifically, beautiful is better than ugly and readability counts together indicate that how one’s code looks matters. Python prioritizes readability in its syntax (relative to other programming languages) and adheres to the idea that “code is more often read than it is written.” As such, those who program in Python are encouraged to consider the beauty and readability of their code. To do so, we’ll cover a handful of considerations here.

Code Consistency#

For very understandable and good reasons, beginner programmers often focus on getting their code to execute without throwing an error. In this process, however, they often forget about code style. While we’ll discuss specific considerations to write well-styled python code in this chapter, the most important overarching concept is that consistency is the goal. Rules help us achieve consistency, and so we’ll discuss a handful of rules and guidelines to help you write easy-to-read code with consistent code style. However, in doing so, we want you to keep the idea of consistency in mind, as programming is (at least partly) subjective. Since it’s easier to recognize & read consistent style, do your best to follow the style guidelines presented in this chapter and once you pick a way to style your code, it’s best to use that consistently across your code.

PEP8#

Python Enhancement Proposals (PEPs) are proposals for how something should be or how something shoudl work in the Python programming language. These are written by the people responsible for writing and maintaining the Python programming language. And, PEPs are voted on before incorporation. PEP8, specfiically, is an accepted proposal that outlines the style guidelines for the Python programming language.

PEP8 is an accepted proposal that outlines the style guide for Python.

The general concepts laid out in PEP8 (and in The Zen of Python) are as follows:

  • Be explicit & clear: prioritize readability over cleerness

  • There should be a specific, standard way to do things: use them

  • Coding Style are guidelines: They are designed to help the code, but are not laws

PEP8: Structure#

Throughout this section we’ll highlight the PEP8 guideline, provide an example of what to avoid and hten demonstrate an improvement on the error. Note that for each “what to avoid” the code will execute without error. This is because we’re discussing stylistic rather than programmatic errors here.

Blank Lines#
  • Use 2 blank lines between functions & classes and 1 between methods

  • Use 1 blank line between segments to indicate logical structure

This allows you to, at a glance, identify what pieces of code are there. Using blank lines to separate out components in your code and your code’s overall structure improves its readability.

What to avoid

In this example of what to avoid, there are no blank lines between segments within your code, making it more difficult to read. Note that if two functions were provided here, there would be 2 blank lines between the different function definitions.

def my_func():
    my_nums = '123'
    output = ''
    for num in my_nums:
        output += str(int(num) + 1)
    return output

How to improve

To improve the above example, we can use what you see here, with variable definition being separated out from the for loop, being separated from the return statement. This code helps separate out the logical structures within a function. Note that we do not add a blank line between each line of code, as that would decrease the readability of the code.

# Goodness
def my_func():
    
    my_nums = '123'
    output = ''
    
    for num in my_nums: 
        output += str(int(num) + 1)
    
    return output
PEP8: Indentation#

Use spaces to indicate indentation levels, with each level defined as 4 spaces. Programming languages differ on the speicfics of what constitutes a “tab,” but Python has settled on a tab being equivalent to 4 spaces. When you hit “tab” on your keyboard within a Jupyter notebook, for example, the 4 spaces convention is implemented for you automatically, so you may not have even realized this convention before now!

What to avoid

Here, you’ll note that, while the print() statement is indented, only two spaces are used. Jupyter will alert you to this by making the word print red, rather than its typical green.

if True:
  print('Words.')
Words.

How to improve

Conversely, here we see the accepted four spaces for a tab/indentation being utilized. Again, remember that the functionality of the code in this example is equivalent to that above; only the style has changed.

if True:
    print('Words.')
Words.
PEP8: Spacing#
  • Put one (and only one) space between each element

  • Index and assignment don’t have a space between opening & closing ‘()’ or ‘[]’

What to avoid

Building on the above, spacing within and surrounding your code should be considered. Here, we see that spaces are missing around operators in the first line of code, whereas the second line has too many spaces around the assignment operator. We also see that there are unecessary spaces around the square brackets the list in line two and spaces after each comma missin in that same line of code. Finaly, in the third line of code there is an unecessary space between my_list and the square bracket being used for indexing.

my_var=1+2==3
my_list  =  [ 1,2,3,4 ]
el = my_list [1]

How to improve

The above spacingissues have all been resolved below:

my_var = 1 + 2 == 3
my_list = [1, 2, 3, 4]
el = my_list[1]
PEP8: Line Length#
  • PEP8 recommends that each line be at most 79 characters long

Note that this specification is somewhat historical, as computers used to require this. As such, there are tools and development environments that will help ensure that no single line of code exceeds 79 characters. However, in Jupyter notebooks, the general guideline “avoid lengthy lines of code or comments” can be used, as super long lines are hard to read at a glance.

Multi-line

To achieve this, know that you can always separate lines of code easily after a comma. In Jupyter notebooks, if you hit return/enter on your keyboard after a comma, your code will be aligned appropriately. For example below you see that after the comma in the first line of code, the 6 is automatically aligned with the 1 from the line above. This visually makes it clear that all of the integers are part of the same list my_long_list. Using multiple lines to make your code easier to read is a great habit to get into.

my_long_list = [1, 2, 3, 4, 5, 
                6, 7, 8, 9, 10]

Further, note that you can explicitly state that the code on the following line is a continuation of the first line of code with a backlash (\) at the end of a line, as you see exemplified here:

my_string = 'Python is ' + \
            'a pretty great language.'

One Statement Per Line

While on the topic of line length and readable code, note that while you can often condense multiple statements into one line of code, you usually shouldn’t, as it makes it harder to read.

What to avoid

For example, for loops can syntactically be specified on a single line, as you see here:

for i in [1, 2, 3]: print(i**2 + i%2)
2
4
10

How to Improve

However, in he code above, it’s harder to read at a glance. Instead, what is being looped over should go on the first line with what code is being executed contained in an indented code block on lines underneat the for statement, as this is easier to read than the above example:

for i in [1, 2, 3]:
    print(i**2 + i%2)
2
4
10
PEP8: Imports#
  • Import one module per line

  • Avoid * imports

  • Use the import order: standard library; 3rd party packages; local/custom code

What to avoid

While you may still be learning which packages are part of the standard library and which are third party packages, this will become more second nature over time. And, we haven’t yet discussed local or custom code, but this includes functions/classes/code you’ve written and stored in .py files. This should be imported last.

In this example here, there are a number of issues! First, numpy is a third party package, while os and sys are part of the standard library, so the order should be flipped. Second * imports are to be avoided, as it would be unclear in any resultant code which functionality came from the numpy package. Third, os and sys should be imported on separate lines to be most clear.

from numpy import *

import os, sys

How to Improve

The above issues have been resolved in this set of imports:

import os
import sys

import numpy as np
PEP8: Naming#
  • Use descriptive names for all modules, variables, functions and classes, that are longer than 1 character

What to avoid

Here, single character, non-descriptive names are used.

a = 12
b = 24

How to Improve

Instead, python encourages object names that describe what is stored in the object or what the object is or does.

This is also important when you want to change an object name after the fact. If you were to “Find + Replace All” on the letter a that would change every single a in your code. However, if you “Find + Replace All” for n_filters, this would likely only change the places in your code you actually intended to replace.

n_filters = 12
n_freqs = 24

Naming Style

  • CapWords (leading capitals, no separation) for Classes

  • snake_case (all lowercase, underscore separator) for variables, functions, and modules

Note: snake_case is easier to read than CapWords, so we use snake_case for the things (variables, functions) that we name more frequently.

What to avoid

While we’ve been using this convention, it’s important to state it explicitly here. Pythonistas (those who program in python) expect the above conventions to be used within their code. Thus, if they see a function MyFunc, there will be cognitive dissonance, as CapWords is to be used for classes, not functions. The same for my_class; this would require the reader of this code to work harder than necessary, as snake_case is to be used for functions, variables, and modules, not classes.

def MyFunc():
    pass
    
class my_class():
    def __init__():
        pass

How to Improve

Intead, follow the guideline above. Also, note that we’ve added two lines between the function and class definitions (to follow the guideline earlier in this chapter).

def my_func():
    pass


class MyClass():
    def __init__():
        pass
String Quotes#

In Python, single-quoted strings and double-quoted strings are the same. Note that PEP8 does not make a recommendation for this. Rather, you are encouraged to be consistent: pick a rule and stick to it. (The author of this books is exceptionally bad at following this advice.)

One place, however, to choose one approach over another is when a string contains single or double quote character string literal. In this case, use the other one that’s not included in the string to avoid backslashes in the string, as this improves readability. For example…

What to avoid

As you see below, you could use a backslash to “escape” the apostraphe within the string; however, this makes the string harder to read.

my_string = 'Prof\'s Project'

How to Improve

Instead, using double quotes to specify the string with the apostraphe (single quote) inside the string leads to more readable code, and is thus preferable.

my_string = "Prof's Project"

PEP8: Documentation#

While documentation (including how to write docstrings and when, how and where to include code comments) will be covered more explicitly in the next chapter, we’ll discuss the style considerations for including code comments and docstrins at this point.

PEP8: Comments#

First, out-of-date comments are worse than no comments at all. Keep your comments up-to-date. While we encourage writing comments to explain your thinking as you’re writing the code, you want to be sure to re-visit your code comments during your “editing” and “improving code style” sessions to ensure that what is stated in the comments matches what is done in your code to avoid confusion for any readers of your code.

Block comments

Block comments are comments that are on their own line and come before the code they intend to describe. They follow the following conventions:

  • apply to some (or all) code that follows them

  • are indented to the same level as that code

  • each line of a block comment starts with a # and a single space

What to avoid

In the function below, while the code comment does come before the code it describes (good!), it is not at the same level of indentation of the code it describes (not good!) and there is no space between the pound sign/hashtag and the code comment:

import random

def encourage():
#help try to destress students by picking one thing from the following list using random
    statements = ["You've totally got this!","You're so close!","You're going to do great!","Remember to take breaks!","Sleep, water, and food are really important!"]
    out = random.choice(statements)
    return out

encourage()
"You're going to do great!"

How to Improve

Intead, here, we see improved code comment style by 1) having the block comment at the same level of indentation as the code it describes, 2) having a space in between the # and the comment, and 3) breaking up the comment onto two separate lines to avoid having a too-long comment.

The code style is also further improved by considering spacing within the statements list and considering line spacing throughout the function.

def encourage():
    
    # Randomly pick from list of de-stressing statements
    # to help students as they finish the quarter.
    statements = ["You've totally got this!", 
                  "You're so close!", 
                  "You're going to do great!",
                  "Remember to take breaks!",
                  "Sleep, water, and food are really important!"]
    
    out = random.choice(statements)
    
    return out

encourage()
"You're so close!"

Inline comments

Inline comments are those comments on the same line as the code they’re describing. These are:

  • to be used sparingly

  • to be separated by at least two spaces from the statement

  • start with a # and a single space

What to avoid

For example, we’ll avoid inline comments that 1) are right up against the code they describe and 2) that fail to have a space after the #:

encourage()#words of encouragement
'Sleep, water, and food are really important!'

How to Improve

Instead, we’ll have two spaces after the code, and a space after the #:

encourage()  # words of encouragement
"You're going to do great!"
PEP8: Documentation#

We’ll cover docstrings in the following chapter, so for now we’ll just specify that PEP8 specifies that a descriptive docstring should be written and included for all functions & classes. We’ll discuss how to approach this shortly!

Exercises#

Q1. Considering code style, which of these is best - A, B, or C?

A)

def squared(input_number):
    val = input_number
    power = 2    
    output = val ** power
    return output

B)

def squared(input_number, power=2):
    
    output = input_number ** power
    
    return output

C)

def squared(input_number):
    
    val = input_number
    power = 2
    
    output = val ** power
    
    return output

Q2. Which of the following uses PEP-approved spacing?

A) my_list=[1,2,3,4,5]
B) my_list = [1,2,3,4,5]
C) my_list  =  [1, 2, 3, 4, 5]
D) my_list=[1, 2, 3, 4, 5]
E) my_list = [1, 2, 3, 4, 5]

Q3. If you were reading code and came cross the following, which of the following would you expect to be a class?

A) Phillies_Game
B) PhilliesGame
C) phillies_game
D) philliesgame
E) PhIlLiEsGaMe

Q4. If you were reading code and came cross the following, which of the following would you expect to be a function or variable name?

A) Phillies_Game
B) PhilliesGame
C) phillies_game
D) philliesgame
E) PhIlLiEsGaMe

Q5. Which of the following would not cause an error in Python and would store the string You’re so close! ?

A) my_string = "You're so close!"
B) my_string = "You"re so close!"
C) my_string = 'You''re so close!'
D) my_string = "You\\'re so close"
E) my_string = 'You're so close!'

Q6. Identify and improve all of the PEP8/Code Style violations found in the following code:

def MyFunction(input_num):
    
    my_list = [0,1,2,3]
    if 1 in my_list: ind = 1
    else:
      ind = 0
    qq = []
    for i in my_list [ind:]:
        qq.append(input_num/i)
    return qq

Q7. Identify and improve all of the PEP8/Code Style violations found in the following code:

def ff(jj):
    oo = list(); jj = list(jj) 
    for ii in jj: oo.append(str(ord(ii)))
    return '+'.join(oo)