PythonLinting & Formatting (black, ruff)

Linting & Formatting (black, ruff)

As a codebase grows and more people touch it, two problems show up that have nothing to do with whether the code works: inconsistent style (tabs vs spaces, quote style, line length) and small mistakes that a computer could catch faster than a human reviewer (unused imports, undefined names, overly complex functions). Formatters solve the first problem by rewriting your code into one consistent style automatically. Linters solve the second by statically analyzing your code for issues without running it.

Linting vs formatting
  • Linting analyzes code for style violations, unused imports/variables, undefined names, likely bugs (e.g. mutable default arguments), and excessive complexity — it reports problems.

  • Formatting rewrites your source code into a single canonical style — spacing, line breaks, quote style, trailing commas — it fixes style automatically so nobody has to think about it or argue about it in review.

  • The two are complementary: a formatter won't catch an unused import, and a linter usually won't rewrite your code for you (though some linters can auto-fix a subset of issues).

black: the standard formatter

black is a deliberately opinionated formatter — it has almost no configuration options, by design. You don't decide how your code should look; black decides, consistently, every time. The trade-off is that teams stop debating style entirely, because there's nothing left to debate.

Bash
pip install black

black .           # reformat every file in the project in place
black --check .    # exit non-zero if any file is not already formatted (for CI)
black --diff .      # show what would change, without writing anything
Note
Run `black --check .` in CI rather than `black .` — you want the pipeline to fail and tell a contributor to run `black .` locally, not to silently rewrite their commit for them.
ruff: a fast, modern linter

ruff is a linter (and formatter) written in Rust that has quickly replaced older tools like flake8, isort, pyupgrade, and several others — because it reimplements most of their checks in a single binary that runs 10-100x faster. Instead of installing and configuring five separate tools, most projects now just install ruff and enable the rule sets they want.

Bash
pip install ruff

ruff check .          # report lint issues
ruff check --fix .     # auto-fix everything that can be safely auto-fixed
ruff format .          # ruff also ships a black-compatible formatter

Older tool

What it did

Replaced by ruff?

flake8

style + basic bug checks

Yes

isort

sorts and groups imports

Yes

pyupgrade

rewrites code to use newer Python syntax

Yes

pep8-naming

naming convention checks

Yes

mypy

static type checking

No — type checking is a different kind of analysis (see the type-checking topic)

Configuring both in pyproject.toml

Both tools read their configuration from pyproject.toml, so a project typically needs no extra config files.

TOML
[tool.black]
line-length = 88
target-version = ["py311"]

[tool.ruff]
line-length = 88
target-version = "py311"

[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B"]   # pycodestyle, pyflakes, isort, pyupgrade, bugbear
ignore = ["E501"]                       # black already enforces line length

[tool.ruff.lint.isort]
known-first-party = ["myproject"]
Wiring into pre-commit

Running black and ruff manually works, but it's easy to forget. Most teams wire both into a pre-commit hook using the pre-commit framework, configured in a .pre-commit-config.yaml file at the repo root. Once installed, every git commit runs black and ruff automatically against the files being committed, blocking the commit if there are unfixed issues.

Bash
pip install pre-commit
pre-commit install        # sets up the git hook, one time per clone
pre-commit run --all-files   # run against the whole repo manually
Why this matters for a team codebase
  • Smaller, cleaner diffs — nobody's pull request reformats unrelated lines just because their editor uses different settings.

  • No bikeshedding — style is not a matter of opinion once black and ruff are the source of truth; there is nothing to argue about in review.

  • Faster code review — reviewers spend their attention on logic and design, not tabs, quotes, or import order.

  • Consistent CI gateblack --check . and ruff check . can both run in CI, failing the build before a style issue or lint error ever reaches main.

Tip
Adopt `black` and `ruff` on day one of a new project — retrofitting them onto a large existing codebase means one huge, noisy "reformat everything" commit. It's worth doing, but it's much easier early.