Skip to content

Our Style

Philosophy

Good code is written once and read many times, so design for readability. Even if you don’t intend anybody else to read your code, there’s still a very good chance that somebody will have to stare at your code and figure out what it does: That person is probably going to be you, twelve months from now.

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!

Some Syntax Considerations

Import Statements

Use import statements for packages and modules only, not for individual classes or functions.

Note: that there is an explicit exemption for imports from the typing module.

Correct

import x  # For importing packages and modules.
from x import y  # Where x is the package prefix and y is the module name with no prefix.
from x import y as z  # If two modules named y are to be imported or if y is inconveniently long.
import y as z  # Only when z is a standard abbreviation, e.g. import numpy as np

Imports should usually be on separate lines:

Correct

import os
import sys

Wrong

import os, sys

However, it is okay to do this:

Correct

from subprocess import Popen, PIPE

Imports are always put at the top of file, just after module comments and docstrings, but before constants. Imports should be grouped in the following order:

  1. Standard library imports.
  2. Related third party imports.
  3. Local application/library specific imports.

A blank line should be placed between groups.

Naming Convention

When naming anything, the names that you give should be meaningful and descriptive. Avoid using throwaway names such as "temp". They should also follow the naming convention below:

  • package_name
  • module_name
  • ClassName
  • method_name
  • ExceptionName
  • function_name
  • CONSTANT_NAME
  • var_name
  • function_parameter_name
  • local_var_name

Constants

Project-wide constants should be placed in a common repository. e.g.

from frb_common import constants

Global Variables

Avoid package wide global variables, but rather use module-level constants, which are encouraged.

Exceptions

Exceptions are allowed but must be used carefully since they break the normal flow control of the code. Avoid using catch all except or catch Exception.

def connect(self, port: int):
    """Connect to a port

    Parameters
    ----------
    port : int
      Port number

    Raises
    ------
    ConnectionError

    Returns
    -------
    connection: object-type
    """
    try:
        connection = self._connect_routine(self, port)
    except ConnectionError as error:
        log.error(error)
    return connection

Type Annotations

Get to know PEP 484. You are not required to annotate all the functions in a module, but: - Use judgment to get to a good balance between safety and clarity on the one hand, and flexibility on the other. - Annotate code that is prone to type-related errors (previous bugs or complexity). - Annotate code that is hard to understand. - Annotate code as it becomes stable from a types perspective. In many cases, you can annotate all the functions in mature code without losing too much flexibility.

def another(thing: list) -> list:
    return thing


def something(self, first_var: int) -> int:
    pass

Indentation

Indentations should use four spaces per indentation level.

Correct

# Aligned with opening delimiter:
foo = long_function_name(var_one, var_two,
                         var_three, var_four)

# Add 4 spaces (extra indentation level) to distinguish args
def long_function_name(
        var_one, var_two, var_three, 
        var_four):
    print(var_one)

# Hanging indents should add a level
foo = long_function_name(
    var_one, var_two, 
    var_three, var_four)

Wrong

# Arguments on first line forbidden without vertical alignment
foo = long_function_name(var_one, var_two,
    var_three, var_four)

# Further indentation required as indentation not distinguishable
def long_function_name(
    var_one, var_two, var_three,
    var_four):
    print(var_one)

The closing brace/bracket/parenthesis on multi-line constructs may be lined up with the first non-whitespace character:

Correct

my_list = [
    1, 2, 3,
    4, 5, 6,
    ]

or it may be lined up under the first character the starts the multi-line construct:

Correct

my_list = [
    1, 2, 3,
    4, 5, 6,
]

Binary Operators and Line Breaks

Previously, the recommended style was to break after the binary operator, but this makes the eye do extra work to tell which are added and which are subtracted:

Wrong

# Operators sit far from operands
income = (gross_wages +
          taxable_interest +
          (dividends - qualified_dividends) -
          ira_deduction -
          student_loan_interest)

However, following tradition from mathematics results in more readable code:

Correct

# Easy to match operators and operands
income = (gross_wages
          + taxable_interest
          + (dividends - qualified_dividends)
          - ira_deduction
          - student_loan_interest)