Definition
Two people write the same function. One puts spaces around the equals sign, the other does not: the program behaves the same, only the look of the file changes. That is where the arguments begin.
black exists to cut them short. It is a Python code formatter: it reads a file, rebuilds its structure, then rewrites it in a single style. It fixes no bugs and never changes what the program does, only the way it is written.
Above all, there is almost nothing to configure, and that is on purpose: a style you are allowed to argue about becomes something to argue about.
pip install black
black my_file.py
black .Installed with pip, it runs as one command, straight on the file. The name comes from the Ford Model T, available in any colour as long as it was black, and it applies PEP 8 to close the style discussion rather than to feed it.
The problem it actually solves
Uneven style crashes no program, and that is why it is allowed to settle in. The cost lies elsewhere, in two places.
In code review first: when nothing enforces layout, half the remarks land on whitespace, and the reviewer skims over what mattered.
In the history next: reindenting a file along the way produces forty modified lines for a change that touched one, and the real change disappears in the batch.
black wipes out both at once, not because its style would be better, but because it is the same for everyone and not open to debate. Two files written in two different ways come out identical, down to the character.
What it decides for you
The first pass over an existing file always comes as a surprise. Here is a function written by hand, then the same one after black.
# Before: random spacing
def invoice(client,lines,discount = 0,currency='EUR'):
total=sum( l['price'] for l in lines )
return {'client':client,'total':total*(1-discount),'currency':currency}
# After black: one possible style
def invoice(client, lines, discount=0, currency="EUR"):
total = sum(l["price"] for l in lines)
return {"client": client, "total": total * (1 - discount), "currency": currency}Single quotes turned into double ones, spacing around operators was normalised, the stray spaces inside the parentheses are gone. A line running past 88 characters would have been wrapped, and none of these decisions is a setting you could reverse.
Is anything left to keep control over? One point, and one only. A comma left after the last item of a list or of a function call reads as an instruction, and black then puts one item per line. Remove it and everything packs back together as soon as there is room.
Without that mechanism in mind, the tool looks like it decides at random: the same call spreads over six lines, then fits on a single one.
Where layout carries meaning, a matrix aligned by hand for instance, # fmt: off suspends black and # fmt: on puts it back to work.
What it does not do, and who handles that
The most frequent mistake is to expect a quality check from it. black reports neither an unused import, nor a variable that is never read, nor a call made on the wrong type: it moves text around, it passes no judgement on the code.
It even sets one condition: faced with a SyntaxError, it stops without rewriting anything, unable to parse the structure it would have to rebuild.
Every check it declines belongs to another tool, and here is what each one hands back to you.
| Need | Tool | What it produces |
|---|---|---|
| Layout | black | A rewritten file |
| Defects and leftovers | ruff | A list of warnings |
| Type consistency | mypy | type hint errors |
| Actual behaviour | pytest | Tests that pass or fail |
The honest comparison is no longer with the older formatters but with ruff format, which reproduces that style bar a few cases and far faster. If ruff is already there for analysis, black is redundant.
The classic adoption mistake
Running black . on a living repository rewrites three hundred files at once. The program still works, but every line now carries your name and today's date: the tool that finds the author of a decision only points back to you.
Worse, a commit mixing a bug fix with the reformatting of the whole folder is unreadable: the reviewer hunts for three useful lines among a thousand, then approves without checking anything.
The clean method takes three steps. Format inside a commit that holds formatting and nothing else. Record its hash in .git-blame-ignore-revs, which the blame tool will walk through as if it were not there. Lock the rest down, finally, in continuous integration, where the check rejects an unformatted file.
black --check --diff .
git config blame.ignoreRevsFile .git-blame-ignore-revsFrequently asked questions
Can the style of black be changed?
Very little, and that is deliberate: line length can be changed, quote normalisation can be switched off, everything else is frozen. Those settings sit in pyproject.toml, the file poetry already uses. Every extra option would reopen the discussion black exists to close.
Should black be installed in every project?
Yes, inside the project virtual environment and with a pinned version. The style shifts slightly from one major version to the next: two mismatched machines reformat the file each in their own way, and the diff war starts again.
Can black break my code?
It is very unlikely: it compares the structure of the rewritten file with the original and rejects the output on the slightest mismatch. One nuance does exist: it cleans up trailing whitespace inside a docstring, which can break a test comparing that string character by character.