Write clean Python code with Black

Black is a code formatter that automatically adjusts your Python code after a well-defined rule set. In this article, I discuss the need for such a code formatter, show you how to integrate it into your IDE and also into your CI pipeline.

Why you want to use a code formatter

When you are a developer working as part of a team on a large project, you might have already realized there are different opinions on how particular structures in your code should be formatted. You may for example have an argument about whether it is better to use single quotes (') or double quotes (") for strings in Python (even though they are completely interchangeable).

Each such argument takes up valuable time that could be spent fixing bugs or building new features. However, when everyone decides for themselves how to handle such formatting decisions, the codebase becomes messy and inconsistent. Therefore, many teams have adopted style guides – wiki pages where such formatting conventions are written down for everyone to follow. Unfortunately, this not only also takes a lot of time, but it is rarely the case that all developers remember that guide by heart and never break any rules after reading them.

Black developed by Łukasz Langa (and similar auto-formatters such as Prettier for JavaScript or gofmt for Go) solves this problem by automatically formatting your code according to a fixed set of rules. They are opinionated, meaning there is little to no space for customization. Their ratio for this decision is that as often, perfect should not be the enemy of good. There is more value in not discussing such rules at all and saving time than in finding the styling rules that would match the team’s preferences a little better.

How Black works

The way Black works is that it parses the Python code, applies its formatting rules and then verifies whether the formatted output still creates the same internal representation in Python. This last step makes Black very safe to use – it will never apply changes that could result in a different interpretation of the code itself.

The setup of Black is very simple:

pip install black

Since there is (almost) nothing to configure, you are already prepared to format your code.

There are two ways of operating the black tool once it is installed. By invoking black --check file_name.py it will do a dry-run and just output whether it would make changes to the file or not (very useful for CI pipelines as will be shown later). By running black file_name.py, the formatting changes are applied automatically:

Also, there are integrations for all major IDEs so you don’t have to manually run the executable. For example in VSCode, you can set the Python formatter to Black and then run the “Format” command to automatically invoke Black inside your editor:

Running Black in a CI pipeline

The setup I’ve shown you until now is probably sufficient if you are a solo developer. However, in a larger team there is always the possibility of someone new joining who is not yet familiar with Black and might be tempted to contribute code that does was not autoformatted. To solve this, you can add a black –check task to your CI pipeline. This will not correct the mistake automatically, but at least it prevents unformatted code to enter your code base.

Here is a minimum viable template for accomplishing such a check in GitLab (but the principle is the same for any CI pipeline):

image: python:latest

variables:
  PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"

stages:
  - Static Analysis

cache:
  paths:
    - .cache/pip
    - venv/

before_script:
  - python --version
  - pip install virtualenv
  - virtualenv venv
  - source venv/bin/activate
  - pip install black

black:
  stage: Static Analysis
  script:
    - black --check .

The pipeline defines a “Static Analysis” stage with a single task (“black”) that performs a black --check on the whole source directory. If it detects unformatted files, it will have a non-zero exit code and cause the pipeline to fail.

 

Bernhard Knasmüller on Software Development