Any Formatter You Like, As Long As It's Black

Programming, Talk Write-Up, Conference Notes

This is a write up of a talk I gave at the Python Pizza Conf in Berlin, on 23 February 2019. The slide deck is availible here.

Before I start talking about Black, let's first talk about code-styles, specifically what are they and why are they useful. A code-style is a set of guidelines, idioms and patterns principally concerned with making code readable to us, as writers, reviewers and maintainers. This is important because when we read code, we want to understand what it is doing functionally, and inconsistencies in style and styles that make code difficult to tease out get in the way of this. At best, it can lead to slowing down of the reading process, at worst it can lead us to misunderstand what a piece of code is doing. It's easier to reason about structures when you can see them clearly.

This is doubly important in Python where the style of the code actually influences it's functionality. Luckily, Python developers have a standard style guide that we can deploy: the PEP-8.

PEP-8, written by Guido van Rossum, Barry Warsaw and Nick Coghlan, is huge set of guidelines on everything that keeps a Python developer up at night: indentation patterns, use of whitespace, when to use trailing commas, everything. One of the things that the PEP 8 guide insists upon is the value of consistency, saying:

A style guide is about consistency.  Consistency with this style guide is important.  Consistency within a project is more important. Consistency within one module or function is the most important.[1]

This emphasis on consistency is, for me at least, the most valuable thing about Black.

Fordist Python

So, what is Black? Black is a Python code formatter. But, why use it? Is it super configurable, letting you fine tune it to produce the exact code-style you want?

Well, no. The opposite. Black is opinionated, uncompromising and has very little in the way of configuration. The only options it currently offers are:

  • Line length
  • Skipping normalisation of string quotes or prefixes.
  • Skipping normalisation of underscores in numeric literals.

There's a distinct reason for this, and it's to do with the emphasis on consistency. Blackened code looks the same, between functions and modules and projects. Blackened code, for the most part, looks the same no matter who is deploying it. This consistency in blackened code means that you sacrifice customisability for standardisation, bringing us to the slogan of Black:

Any formatter you like, as long as it's Black.

This is a play on Henry Ford's famous quote[2] about the Model T car back in 1909. Fordism as a way of organising production is significant for it's emphasis on standardised, consistent components which facilitated mass production and brought about a "dynamism and maximum adaptability of production methods".[3] The consistency of standardised components lets you invest more effort into combinations of those components. Black operates under similar principles: it removes the effort you have to invest into thinking about how to format your python code, which is now standardised, so you can then instead focus your efforts into combinations of standardised code. The consistency of Black means that the code you commit, maintain and review will look the same, letting you focus on the content of those tasks. Uniformity of code takes precedence over perfect, infinitely tweakable formatting.

Blackened Focus

Which components of PEP-8 does the Black code style focus on in order to achieve this? It places an emphasis on the styling of:

  • Horizontal Whitespace
  • Vertical Whitespace
  • Line Length
  • Consistency

With horizontal whitespace, we're concerned with where to put spaces and where to avoid them. Here, we just do what PEP-8 commands. Similarly, with vertical whitespace we also just follow PEP-8 guidelines on which tokens to break on and how many empty lines to use. When it comes to pairs of brackets, if it does not fit on a line, we always break after the last element and de-indent the bracket. This transforms function signatures from:

def a_nice_function(param_a: str, param_b: dict, path: os.PathLike, verbose: bool = False):
    with open(path, "w") as file:
        pass

into:

def a_nice_function(
    param_a: str,
    param_b: dict,
    path: os.PathLike,
    verbose: bool = False,
):
    with open(path, "w") as file:
        pass

Which brings them in line with how we'd expect dictionaries and lists to be formatted. This also applies to if statements too:

if (
    this_long_variable_is_true
    and this_other_variable == 10
    or this_other_variable > 100
    and this_function_call() < 100
):
    pass

This has the added benefit of visually delimiting the function signature from the body and the conditions of the if from the consequences of it.

With line length, we diverge from PEP-8, which suggests that a strict limit of 79 characters per line. Instead, Black seeks a middle ground between conservative limits of 79 and maybe-too-large-limits of 120 by going for 88, based on Raymond Hettinger's PyCon 2015 talk 'Beyond PEP-8', where he suggests a line-length of "90-ish"[4]. In many instances, using if statements within for loops within functions within classes has already lost you a bunch of line-space in the form of indentation. A 90-ish line length gives you some breathing room, without the lines getting too long.

Consistency we've already touched upon, but it helps to see some concrete examples. The addition of trailing commas, unless the collection fits on one line, is consistent throughout function signatures, lists, dictionaries, wherever it pops up. This has a nice side-effect that if you were to add an item to a collection within a git commit, the only line change would be the line you added, because the trailing comma already existed on the previous item. It's a minor thing, but makes a difference in big pull requests. You only have 1 line change with all the semantic content, instead of 2 lines of change with 1 completely lacking any semantic content.

We're also consistent with the use of double quotes instead of single quotes in strings, such as:

my_var = f"Hello world! I am {user}"

This anticipates the use of single quotes, so that in future if you wanted to change the string to:

my var = f"Hello world! I'm {user}"

You wouldn't have to change what kind of quotes you're wrapping your strings in, in order to use an apostrophe there.

These 4 focuses of Black produce code that looks the same wherever it is written, that is consistent and produces far less noise when reading, either on its own or in pull requests. Though you lose flexibility and customisability, you gain speed and efficiency when writing and reading code. A worthwhile trade-off, personally.

Carbonize Your Code

So, if achieving consistent formatting without any of the hand holding a lot of other formatters force on you sounds interesting, you can install black via pip install black. Running black formats files in place, but you can pass the --check or --diff flags in order to just check whether formatting will take place (useful in tests) or to just see what would be changed.

Black is used as the code formatter in a bunch of cool projects like virtualenv, pytest and PyPI and is currently sitting at 7800 stars on Github with about 1.3 million downloads. Carbonize your code!


  1. Van Rossum, G., Warsaw, B. and Coghlan, N. (2001). PEP 8 -- Style Guide for Python Code. [online] Python.org. Available at: https://www.python.org/dev/peps/pep-0008/#a-foolish-consistency-is-the-hobgoblin-of-little-minds [Accessed 20 Feb. 2019].
  2. Ford, H. (1922). My Life and Work. New York: Doubleday, Page & Company.
  3. Kumar, K. (2009). From Post-industrial to Post-modern Society: New Theories of the Contemporary World. Oxford: Blackwell.
  4. YouTube. (2015). Raymond Hettinger - Beyond PEP 8 -- Best practices for beautiful intelligible code - PyCon 2015. [online] Available at: https://www.youtube.com/watch?v=wf-BqAjZb8M [Accessed 20 Feb. 2019].