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.
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
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.
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.
[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.
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 gate —
black --check .andruff check .can both run in CI, failing the build before a style issue or lint error ever reachesmain.