Since last year, I’ve been a teaching assistant in the Introduction to programming for physicists course at The University of Manchester. This is taught in the first semester of Y2 and we introduce them to Python. While marking the students’ assignments, we have noticed that most of the deductions were made in style. All these are common mistake, but when accumulated, they make the code hard to read. Here are a few notes on coding style. More can be find from PEP8 or Google Python Style Guide.

Structure your code

A good code structure would be the following:

  1. header
  2. import statements
  3. constant definitions
  4. function definitions
  5. main code

It should contain a title (e.g. the name of the programme), an author and a date. This allows the reader to quickly understand the purpose of the code, without having to read all the lines in the file. Including the author name serves for copyright, but importantly, indicates who to contact for a question or any request about the code. The date serves to check whether the code is up-to-date.

Example:

# -*- coding: utf-8 -*-
"""
example_header.py
The purpose of this code is to show an example of a header.
Note the first line about encoding. It is good practice to keep it as
it allows some IDE to enforce the encoding and prevent some bugs.
This is mostly a Python 2 feature.

Last updated: 31oct 2019
Julien Barrier - julien.barrier@manchester.ac.uk

"""

Constant definitions.

In python, we call constants global variables. In other languages (e.g. JavaScript, C), constants cannot be changed throughout the document. This is not the case in python, so we place them at the top of the document to state that they are not supposed to be changed. They are useful because it allows us, in this course, to set values specific to the problem we want to solve. When evaluating, we — the examiner — may change these constants and check the program result. It is much easier to have them early than trawling through the code to update every occurrence. In python, we define constant names with the following convention: CAPITALIZED_WITH_UNDERSCORES.

Function definitions.

It is good practice to define a comment inside the function, to describe quickly what the function does and tell the variable type for the input and output arguments.

Example:

def joule_to_ev(value_joule):
    '''
    Converts an energy value from Joule to eV
    args: value_joule (float)
    '''
    value_ev = value_joule/1.60e-19
    return value_ev

Name your functions/variables

All the names for functions/variables should be self explanatory. They should be written in full English. For example, it is always better to write mass = 3.0 rather than m = 3.0. Function names should be written using snake_case in Python. In other languages, different conventions are used (like camelCase in JavaScript, for example).

Ideally, you should look for self explanatory names, meaningful and concise.

Line breaks

maximum line length

It is common to have no longer than 79 characters on a line, so that all the code is visible without having to scroll horizontally, on any screen.

break a line within a function

The convention used when writing a function is to include an extra level of indentation for the variable names.

Example:

def function(
        variable1, variable2, variable_with_a_very_long_name, variable3,
        variable5):
    # default indentation level for the function

break a line in an expression

The convention is to break the line before the mathematical operator, and align it to the innermost parenthesis. When you call a function, break the line at the coma, and the next line should start at the level of the opening parenthesis. When breaking the line in a middle of mathematical operation, it should be done such as the next line starts with the operator at the same level as the previous term. Most of the time, the whole expression should be wrapped up within parenthesis (or eventually a backslash \ ), to ensure Python understands.

Blank lines

The amount of blank lines should be adjusted carefully. If there are too much, the code looks sparse and is hard to read. If there are too few and you have only one block of code, it becomes hard to analyse. Function definitions should be surrounded by two blank lines above and below. Otherwise, use blank lines to show the structure of the code and separate different logics.

Spacing

Whitespaces should be included as in standard typography rules. That means they should not be included:

  • before a coma, semicolon or colon;
  • before or after a parenthesis, bracket or brace;
  • before the open parenthesis/bracket starting a function, an argument list a list indexing or slicing.
  • when passing a kwarg (keyword argument, e.g. plt.plot(x, y, ls='-', lw=.5, c='r'));
  • at the end of a line;
  • to align tokens on consecutive lines, such as =,:,#;

On the other hand, they should be included:

  • around assignment operations (=, +=, *=, etc.)
  • around comparisons operators (==, <, >, !=, <>, <=, >=, in, not in, is, is not)
  • around Booleans (and, or, not);
  • after a hashmark in a comment (#)
  • when a mathematical operation contains different levels of priority, include whitespaces to show priority (e.g. y = x*2 - 1)

Example:

This code calculates a value of π using a Monte Carlo method: This code is not the most efficient but shows the general structure we expect in the Introduction to programming for physicists course.

# -*- coding: utf-8 -*-
"""
Monte Carlo pi
Program that calculates a value for pi using a Monte Carlo method

Last updated: 31oct 2019
Julien Barrier - julien.barrier@manchester.ac.uk
"""

import random
import math
import matplotlib.pyplot as plt

ITERATIONS = 1000

def check_unit_norm(x,y):
    """
    Checks if inputs are inside the unit circle
    x (float)
    y (float)
    returns Boolean
    """
    if math.sqrt(x**2 + y**2) <= 1:
        return True

counter = 0

for i in range(1,ITERATIONS):

    # we pick a random X,Y position
    X = random.random()
    Y = random.random()

    if check_unit_norm(X, Y):
        COUNTER += 1;
        plt.plot(X, Y, 'bo')
    else:
        plt.plot(X, Y, 'ro')

    # we print out the current value of pi after every 100th point
    if i > 0 and i % 100 == 0:
        print ("Currently %9.7f\n" % (4 * counter / i))

pi = 4 * counter / ITERATIONS
print("A value for pi is %9.7f\n" % pi)