Skip to content

Styling and Formatting Code


Style and formatting conventions to keep our code looking consistent.
Goku Mohandas
· ·
Repository

📬  Receive new lessons straight to your inbox (once a month) and join 35K+ developers in learning how to responsibly deliver value with ML.

Intuition

Code is read more often than it is written. -- Guido Van Rossum (author of Python)

When we write a piece of code, it's almost never the last time we see it or the last time it's edited. So we need to explain what's going on (via documentation) and make it easy to read. One of the easiest ways to make code more readable is to follow consistent style and formatting conventions. There are many options when it comes to Python style conventions to adhere to, but most are based on PEP8 conventions. Different teams follow different conventions and that's perfectly alright. The most important aspects are:

  • consistency: everyone follows the same standards.
  • automation: formatting should be largely effortless after initial configuration.

Tools

We will be using a very popular blend of style and formatting conventions that makes some very opinionated decisions on our behalf (with configurable options).

  • Black: an in-place reformatter that (mostly) adheres to PEP8.
  • isort: sorts and formats import statements inside Python scripts.
  • flake8: a code linter with stylistic conventions that adhere to PEP8.

Install required packages:

pip install black==22.3.0 flake8==3.9.2 isort==5.10.1

Since these styling packages are are not integral to the core machine learning operations, let's create a separate list in our setup.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# setup.py
style_packages = [
    "black==22.3.0",
    "flake8==3.9.2",
    "isort==5.10.1"
]

# Define our package
setup(
    ...
    extras_require={
        "dev": docs_packages + style_packages,
        "docs": docs_packages,
    },
)

Unlike docs, we don't add style_packages as a separate extras_require because there is no need for someone to install just the style packages. So we can just add it to the dev option.

Configuration

Before we can properly use these tools, we'll have to configure them because they may have some discrepancies amongst them since they follow slightly different conventions that extend from PEP8.

Black

To configure Black, we could just pass in options using the CLI method, but it's much more efficient (especially so others can easily find all our configurations) to do this through a file. So we'll create a pyproject.toml in the root of our project directory with the following:

touch pyproject.toml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# Black formatting
[tool.black]
line-length = 100
include = '\.pyi?$'
exclude = '''
/(
      .eggs         # exclude a few common directories in the
    | .git          # root of the project
    | .hg
    | .mypy_cache
    | .tox
    | venv
    | _build
    | buck-out
    | build
    | dist
  )/
'''

Here we're telling Black that our maximum line length should be 100 characters and to include and exclude certain file extensions.

The pyproject.toml was created to establish a more human-readable configuration file that is meant to replace a setup.py or setup.cfg file and is increasingly adopted by many open-source libraries.

isort

Next, we're going to configure isort in our pyproject.toml file (just below Black's configurations):

1
2
3
4
5
6
7
# iSort
[tool.isort]
profile = "black"
line_length = 79
multi_line_output = 3
include_trailing_comma = true
virtual_env = "venv"

Though there is a complete list of configuration options for isort, we've decided to set these explicitly so there are no conflicts with Black.

flake8

Lastly, we'll set up flake8, but this time we need to create a separate .flake8 file to define its configurations:

touch .flake8
1
2
3
4
5
6
7
8
[flake8]
exclude = venv
ignore = E501, W503, E226
max-line-length = 79

# E501: Line too long
# W503: Line break occurred before binary operator
# E226: Missing white space around arithmetic operator

Here we're including an ignore option to ignore certain flake8 rules so everything works with our Black and isort configurations.

Besides defining configuration options here, which are applied globally, we can also choose to specifically ignore certain conventions on a line-by-line basis. Here is an example of how we utilize this:

1
2
# tagifai/config.py
import pretty_errors  # NOQA: F401 (imported but unused)

By placing the # NOQA: <error-code> on a line, we're telling flake8 to do NO Quality Assurance for that particular error on this line.

Usage

To use these tools that we've configured, we have to execute them from the project directory:

black .
flake8
isort .

black .
All done! ✨ 🍰 ✨
9 files left unchanged.
flake8
python3 -m isort . isort .
Fixing ...

Take a look at your files to see all the changes that have been made!

the . signifies that the configuration file for that package is in the current directory

In our makefile lesson we'll learn how to combine all these commands into one. And in our pre-commit lesson we'll learn how to automatically execute this formatting whenever we make changes to our code.


To cite this lesson, please use:

1
2
3
4
5
6
@article{madewithml,
    author       = {Goku Mohandas},
    title        = { Styling - Made With ML },
    howpublished = {\url{https://madewithml.com/}},
    year         = {2021}
}