Python Coding Style Guide

Basics

Summary

Python3 is the main language used for simple tools. As tools grow in complexity, or the requirements on them develop, they serve as valuable prototypes to re-implement tools or their behaviors more maintainably.

Python can be written in vastly different styles, which can lead to code conflicts and code review latency. This style guide aims to promote Python readability across groups. To quote the C++ style guide: “Creating common, required idioms and patterns makes code much easier to understand.”

This guide defines the lowRISC style for Python version 3. The goals are to:

  • promote consistency across hardware development projects
  • promote best practices
  • increase code sharing and re-use

Terminology Conventions

Unless otherwise noted, the following terminology conventions apply to this style guide:

  • The word must indicates a mandatory requirement. Similarly, do not indicates a prohibition. Imperative and declarative statements correspond to must.
  • The word recommended indicates that a certain course of action is preferred or is most suitable. Similarly, not recommended indicates that a course of action is unsuitable, but not prohibited. There may be reasons to use other options, but the implications and reasons for doing so must be fully understood.
  • The word may indicates a course of action is permitted and optional.
  • The word can indicates a course of action is possible given material, physical, or causal constraints.

Style Guide Exceptions

Justify exceptions with a comment.

No style guide is perfect. There are times when the best path to a working design, or for working around a tool issue, is to simply cut the Gordian Knot and create code that is at variance with this style guide. It is always okay to deviate from the style guide by necessity, as long as that necessity is clearly justified by a brief comment, as well as a lint waiver pragma where appropriate.

A common case where you may wish to disable tool-enforced reformatting is for large manually formatted data literals. In this case, no explanatory comment is required and yapf can be disabled for that literal with a single pragma.

Python Conventions

Summary

The lowRISC style matches PEP8 with the following options:

  • Bitwise operators should be placed before a line split
  • Logical operators should be placed before a line split

To avoid doubt, the interpretation of PEP8 is done by yapf and the style guide is set using a .style.yapf file in the top level directory of the repository. This just sets the base style to pep8 and overrides with the exceptions given above.

In addition to the basic style, imports must be ordered alphabetically within sections:

  • Future
  • Python Standard Library
  • Third Party
  • Current Python Project

The import ordering matches that enforced by isort. Currently the isort defaults are used. If this changes a .isort.cfg file will be placed in the top level directory of the repository.

Lint tool

The lintpy.py utility in util can be used to check Python code. It checks all Python (.py) files that are modified in the local repository and will report problems. Both yapf and isort checks are run.

Basic lintpy usage is just to run from the util directory. If everything is fine the command produces no output, otherwise it will report the problems. Additional information will be printed if the --verbose or -v flag is given.

$ cd $REPO_TOP/util
$ ./lintpy.py
$ ./lintpy.py -v

Checking can be done on an explicit list of files using the --file or -f flag. In this case the tool will not derive the list from git, so any file can be checked even if it has not been modified.

$ cd $REPO_TOP/util
$ ./lintpy.py -f a.py subdir/*.py

Errors may be fixed using the same tool to edit the problem file(s) in-place (you may need to refresh the file(s) in your editor after doing this). This uses the same set of files as are being checked, so unless the--file or -f flag is used this will only affect files that have already been modified (or staged for commit if -cis used) and will not fix errors in Python files that have not been touched.

$ cd $REPO_TOP/util
$ ./lintpy.py --fix

lintpy.py can be installed as a git pre-commit hook which will prevent commits if there are any lint errors. This will normally be a symlink to the tool in util so changes are automatically used (it also works if lintpy.py is copied to .git/hooks/pre-commit but in that case the hook must be reinstalled each time the tool changes). Since git hooks are not automatically installed the symlink hook can be installed if required using the tool:

$ cd $REPO_TOP/util
$ ./lintpy.py --hook

Fixing style errors for a single file can also be done with yapf directly:

$ yapf -i file.py

Fixing import ordering errors for a single file can be done with isort:

$ isort file.py

Yapf and isort are Python packages and should be installed with pip:

$ pip3 install --user yapt
$ pip3 install --user isort

File Extensions

Use the .py extension for Python files

General File Appearance

Characters

Use only UTF-8 characters with UNIX-style line endings("\n").

Follows PEP8.

POSIX File Endings

All lines on non-empty files must end with a newline ("\n").

Line Length

Wrap the code at 79 characters per line.

The maximum line length follows PEP8.

Exceptions:

  • Any place where line wraps are impossible (for example, an include path might extend past 79 characters).

No Tabs

Do not use tabs anywhere.

Use spaces to indent or align text.

To convert tabs to spaces on any file, you can use the UNIX expand utility.

No Trailing Spaces

Delete trailing whitespace at the end of lines.

Indentation

Indentation is four spaces per level.

Follows PEP8. Use spaces for indentation. Do not use tabs. You should set your editor to emit spaces when you hit the tab key.

Executable Python tools

Tools that can be executed should use env to avoid making assumptions about the location of the Python interpreter. Thus they should begin with the line:

#!/usr/bin/env python3

This should be followed by a comment with the license information and the doc string describing the command.

Argument Parsing

Use argparse to parse command line arguments.

In command line tools use the argparse library to parse arguments. This will provide support for --help and -h to get usage information.

Every command line program should provide --version to provide standard version information. This lists the git repository information for the tool and the version numbers of any Python packages that are used. The show_and_exit routine in reggen/version.py can be used to do this.

Options that consume an arbitrary number of command line arguments with nargs="*" or nargs="+" should be avoided wherever possible. Comma separated lists can be passed to form single arguments and can be split after arguments are parsed. Args that allow for lists should mention that capability and the separator using the help keyword. To display proper delimiting of lists, args that allow for lists may demonstrate the separator with the metavar keyword.