Skip to content

Styling and Formatting Code


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

📬  Receive new lessons straight to your inbox (once a month) and join 20K+ 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 both explain what's going on (documentation but also 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 (link walks through the different components (blank lines, imports, etc.) that conventions were written for). You'll notice that different teams will default to different conventions and that's ok. The most important aspects are that everybody is consistently following the same convection and that there are pipelines in place to ensure that consistency. Let's see what this looks like in our application.

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 adheres to PEP8. We can explore examples of the formatting adjustments that Black makes in this demo.
  • isort: sorts and formats import statements inside Python scripts.
  • flake8: a code linter that with stylistic conventions that adhere to PEP8.

We installed all of these as they were defined in out setup.py file under dev_packages.

1
2
3
"black==20.8b1",
"flake8==3.8.3",
"isort==5.5.3",

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. 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 need to create a pyproject.toml file and place the following configurations:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# Black formatting
[tool.black]
line-length = 79
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
  )/
'''

Note

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 widely adopted by many open-source libraries.

Here we're telling Black that our maximum line length should be 79 characters and to include and exclude certain file extensions. We're going to follow the same configuration steps in our pyproject.toml file for configuring isort as well. Place the following configurations right below Black's configurations:

20
21
22
23
24
25
26
27
# iSort
[tool.isort]
profile = "black"
line_length = 79
multi_line_output = 3
include_trailing_comma = true
skip_gitignore = true
virtual_env = "venv"

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

Lastly, we'll set up flake8 but this time we need to create a separate .flake8 file and place the following configurations:

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 setting up some configurations like before but 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 specifically ignore certain conventions on a line-by-line basis. Here are a few example in our code of where we utilized this method:

1
2
3
4
5
6
7
# tagifai/config.py
import pretty_errors  # NOQA: F401 (imported but unused)
...
# app/cli.py
with mlflow.start_run(
    run_name="cnn"
) as run:  # NOQA: F841 (assigned to but never used)

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 could run these commands individually (the . signifies that the configuration file for that package is in the current directory) but we can also use the style target command from our Makefile:

1
2
3
black .
flake8
isort .

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

Note

We may sometimes forget to run these style checks after we finish development. We'll cover how to automate this process using pre-commit so that these checks are automatically executed whenever we want to commit our code.


To cite this lesson, please use:

1
2
3
4
5
6
@article{madewithml,
    title  = "Styling and Formatting Code - Made With ML",
    author = "Goku Mohandas",
    url    = "https://madewithml.com/courses/mlops/styling/"
    year   = "2021",
}