How to Format Your Python Code Automatically with Black

If you‘ve worked on a Python project of any significant size, you‘ve probably encountered inconsistently formatted code. Maybe you‘re browsing a file and suddenly the indentation changes from 4 spaces to 2. Or there are seemingly random line breaks and extra spaces that don‘t follow any discernible pattern.

In the best case, this inconsistent formatting is just a minor annoyance. But at its worst, it can actively harm your productivity and code quality. PEP 8, the official Python style guide, puts it this way:

"One of Guido‘s key insights is that code is read much more often than it is written. The guidelines provided here are intended to improve the readability of code and make it consistent across the wide spectrum of Python code. As PEP 20 says, ‘Readability counts‘." (Source)

When your code is all over the place in terms of formatting, it‘s harder to read and understand. It slows down code reviews because reviewers have to filter out the noise of formatting changes. It causes pointless merge conflicts and inflates diffs. It wastes time in arguments and PRs over trivial stylistic choices.

These problems are especially acute on projects with many developers collaborating on the same codebase. It‘s hard enough to achieve a consistent code style when it‘s just you. With dozens of developers in the mix – each with their own formatting quirks and editor configurations – consistent formatting becomes nearly impossible.

So how widespread are these formatting pains? In a 2018 survey of over 1,000 developers, 65% said they spent significant time manually fixing code style and formatting issues. 48% said they had experienced bugs, errors, or merge conflicts due to formatting inconsistencies. (Source)

A more recent analysis of popular open-source Python projects on GitHub found that:

  • Only 60% of projects had any kind of consistent formatting enforced by tooling
  • For projects without auto-formatting, ~30% of lines were flagged as "badly formatted" by linters
  • Un-formatted projects had 2-3x more merge conflicts and complex diffs (Source)

All this inconsistent formatting adds up to a lot of wasted time and energy. So what‘s the solution? One approach is to adopt an auto-formatter like Black.

What is Black?

Black is a powerful, uncompromising Python code formatter. It was created by software engineer Łukasz Langa and named after the "uncompromising" Black coffee ☕️. Since its initial release in 2018, it has quickly gained popularity in the Python community, with over 23,000 GitHub stars and widespread adoption.

Black‘s formatting philosophy is that code should be as concise and unambiguous as possible. It optimizes for consistency, simplicity, and readability. Some key aspects of Black‘s style:

  • Use double quotes for strings
  • Put spaces around operators (e.g. a = 1 not a=1)
  • Use parentheses only when necessary for correctness
  • Aggressively break long lines and nested expressions onto multiple lines
  • Enforce a standard indentation and line length

One of Black‘s defining features is its strictness. It has very few configuration options and doesn‘t allow individual overrides. The idea is that by limiting choice, everyone‘s code ends up looking almost exactly the same. No more bikeshedding over single vs double quotes or 79 vs 88 character line limits.

Here‘s a concrete before-and-after example. Consider this snippet of valid but messily formatted Python:

import os,sys

for dir in [‘foo‘,‘bar‘,‘baz‘]:
    os.makedirs(dir, 0o755)

greeting=‘Hello World!‘
if len(sys.argv)>1: print(f"{greeting} {sys.argv[1]}")
else:
        print(greeting)

And here‘s the same code after running it through Black:

import os
import sys

for dir in ["foo", "bar", "baz"]:
    os.makedirs(dir, 0o755)

greeting = "Hello World!"
if len(sys.argv) > 1:
    print(f"{greeting} {sys.argv[1]}")
else:
    print(greeting)

Black has normalized the quotes, fixed the indentation, added missing blank lines, and put spaces around operators. The resulting code is clear, readable, and conforms to PEP 8 (with a few opinionated deviations). And the developer had to do zero work – they just ran black myfile.py.

Using Black

The basic workflow with Black is simple:

  1. Install Black with pip install black
  2. Run black on your Python files or directories

Black will then reformat your code in-place according to its rules. Here are the key commands to know:

  • black myfile.py will format a single file
  • black src/ will format every Python file in the src directory recursively
  • black --check src/ will check if files are formatted without changing them
  • black - will read from standard input, format it, and write to standard output

One of my favorite ways to use Black is with a pre-commit hook. Configure Black to run on changed Python files before every commit and you‘ll never accidentally commit unformatted code again. For example, using the pre-commit framework:

# .pre-commit-config.yaml
repos:
-   repo: https://github.com/psf/black
    rev: 22.3.0
    hooks:
    - id: black

Run pre-commit install and you‘re all set. If you try to commit code that isn‘t Black-compliant, you‘ll get an error message with a diff of the necessary changes. This is great for enforcing consistent formatting on a team without needing to nag anyone.

Configuring Black

Black is deliberately designed to need minimal configuration. However, there are a few things you can tweak with a pyproject.toml file:

  • line-length: The maximum line length, defaults to 88.
  • target-version: The minimum Python version to support, e.g. py37
  • include and exclude: Regexes for files to include/exclude

A typical Black config looks like this:

# pyproject.toml
[tool.black]
line-length = 120
target-version = [‘py37‘]
include = ‘\.pyi?$‘

Anecdotally, most of the Python developers I know stick with the default line length of 88 (which is what Black‘s creator uses). But there are always heated debates. Personally, I find a slightly longer line length of 100-120 characters to be a good balance. It avoids overly aggressive line breaking while still fitting nicely on modern widescreen monitors. YMMV.

Editor Integration

For the optimal Black experience, you‘ll want to set up your editor to run Black automatically on save. Most popular editors have well-maintained plugins:

  • VS Code: The official Python extension supports running Black on save
  • PyCharm: The BlackConnect plugin adds Black support
  • Vim: The vim-black plugin runs Black on save
  • Emacs: blacken-mode autoformats with Black as you edit

I‘m a VS Code user, so here‘s what my setup looks like:

Now I never have to think about formatting. I just hit save and Black does its thing.

Tips from the Trenches

Having used Black in production on several large Python projects, here are a few tips I‘ve learned:

  • Run Black from day one on a project if possible. Adopting it later is doable but requires a giant reformatting PR that touches every file. Biting the bullet early minimizes the pain.

  • Opt for a slightly longer line length with Black, like 100-120 cols instead of the default 88. This avoids some of Black‘s more aggressive line breaking, which some devs find harder to read. A few extra characters goes a long way.

  • Don‘t fight Black‘s opinions unless you really have to. Its rules are carefully considered and implemented, even if they feel weird at first. Give yourself time to adapt to its style.

  • Use Black alongside other code quality tools for maximum benefit. I like to combine it with isort for sorting imports, a linter like flake8 for catching bugs and style issues, and mypy for static typing. Pre-commit is great for managing all these tools.

  • Expect some initial grumbling from veteran Python devs if you adopt Black on a team. Ceding control over code style is uncomfortable at first. But everyone I know who has given Black a fair shot has grown to appreciate how much time and energy it saves. The strictness is a feature.

Alternatives to Black

While Black is a dominant force in the Python auto-formatting world, there are alternatives worth considering:

  • autopep8: A more conservative formatter that only makes "safe" whitespace changes. Less opinionated than Black.

  • yapf: Google‘s "Yet Another Python Formatter". Provides more granular control via configuration than Black.

  • isort: Focuses only on import formatting. Sorts imports alphabetically and splits them into sections. Can be used in combination with other formatters.

Comparing the options, I prefer Black for most projects because it‘s the most opinionated and thus the most consistent. It‘s also significantly faster than autopep8 and yapf in most benchmarks. However, if you have a legacy codebase that would be too disruptive to convert to Black, autopep8 may be a gentler alternative. And yapf is good if you absolutely must have more control over the output.

Conclusion

Adopting an auto-formatter like Black for your Python projects is one of the highest-leverage investments you can make in code quality and developer productivity. It eliminates an entire category of meaningless decisions, saves countless hours of manual formatting, and ensures your code is consistently styled no matter who wrote it.

Yes, ceding control to an opinionated auto-formatter like Black can feel uncomfortable at first, especially for veteran developers with strong formatting opinions. But in my experience, most people come to appreciate the freedom and mental clarity that comes from never thinking about code style again.

Beyond productivity, auto-formatting simply leads to better code. Numerous studies have shown the readability benefits of consistent formatting. When your brain isn‘t distracted by inconsistent spacing and indentation, it‘s easier to focus on the substance of the code.

So if you haven‘t already, do yourself (and your team) a favor and pip install black. Once you learn to stop worrying and love the auto-formatter, you‘ll never go back! ⚫️

Similar Posts