Learning Goals
3 min- Distinguish a formatter from a linter.
- Auto-format with
black(orruff format). - Order imports with
isort(or ruff'sIrules). - Use
--checkmode to enforce formatting in CI.
Warm-Up · Formatter vs Linter
5 minLINTER (ruff, pylint) finds problems → you decide how to fix FORMATTER (black, isort) rewrites layout automatically → no decisions
# before black: x = {'a':1, 'b':2} def f( a,b ):return a+b # after black — one canonical style, no arguments: x = {"a": 1, "b": 2} def f(a, b): return a + b
black is "opinionated" on purpose: it has almost no options, so there's nothing to argue about. Run it and your whole team's code looks identical — diffs shrink to real changes, and code review stops nitpicking whitespace.
New Concept · Auto-Formatting
14 minblack — the layout formatter
pip install black black . # reformat everything in place black myfile.py # one file black --check . # don't change anything; exit non-zero if NOT formatted black --diff . # show what WOULD change
--check is what CI uses: it doesn't edit, it just fails the build if any file isn't already black-formatted.
isort — order the imports
# before isort: import os from myapp import thing import sys from collections import OrderedDict # after isort — grouped: stdlib, third-party, local; alphabetised: import os import sys from collections import OrderedDict from myapp import thing
pip install isort isort . isort --check-only . # CI mode
Or just use ruff for both
ruff format . # black-compatible formatter, built into ruff ruff check --select I --fix . # sort imports (replaces isort) # many teams now use ONLY ruff: it lints, formats, and sorts imports.
Configure once
# pyproject.toml [tool.black] line-length = 100 [tool.isort] profile = "black" # make isort agree with black's style
profile = "black" matters — it stops isort and black fighting each other over import formatting.
Format on save (editor)
Configure your editor to run black/ruff-format on every save. Then formatting is invisible — you write messy, save, and it's clean. (VS Code: set the formatter + "format on save".)
Worked Example · Format a Whole Project
12 min# messy.py — before (inconsistent everywhere)
import sys,os
from myapp import (a,b,c)
def calc(x,y,z=10):
result=x*y+z
data={'key1':'value1','key2':'value2','key3':'value3','key4':'value4'}
return result,data$ ruff check --select I --fix . # sort imports $ ruff format . # reformat layout 2 files reformatted
# messy.py — after (one canonical style) import os import sys from myapp import a, b, c def calc(x, y, z=10): result = x * y + z data = { "key1": "value1", "key2": "value2", "key3": "value3", "key4": "value4", } return result, data
Read the diff
Imports sorted and grouped, spaces normalised, the long dict broken across lines, double quotes everywhere — and you made zero decisions. Run this on every save and every file in the repo looks the same forever. The payoff is huge in code review: diffs show only real changes, never whitespace noise.
Try It Yourself
13 minTake a messy file, run black --diff to preview, then black to apply. Review with git diff.
Run isort --profile black (or ruff check --select I --fix) on a file with scrambled imports. Confirm the stdlib/third-party/local grouping.
Run black --check . on an unformatted repo — confirm it exits non-zero (would fail CI). Format it, run again — confirm it passes.
Mini-Challenge · One-Command Quality
8 minSet up a project so a single workflow runs: format (ruff format), sort imports, lint (ruff check), and tests (pytest). Write a short shell script or Makefile target check that runs all four and fails if any fails. This is the local pre-flight every commit should pass.
Show an example
# Makefile check: ruff format --check . ruff check . pytest --cov=myapp --cov-fail-under=80 # run it: $ make check
Non-negotiables: format-check + lint + tests in one command, failing fast on any problem. This becomes your pre-commit hook (next lesson) and CI step (Lesson 46).
Recap
3 minA formatter rewrites layout automatically (no decisions); a linter finds problems (you decide). black/ruff format handle layout, isort/ruff's I rules order imports. Use --check mode in CI. Format on save so it's invisible. Result: consistent code, clean diffs, no whitespace bikeshedding. Next: type checking with mypy.
Vocabulary Card
- formatter
- A tool that rewrites code to a canonical style automatically.
- black
- The opinionated Python formatter — almost no options to argue over.
- isort
- Sorts and groups imports (stdlib / third-party / local).
- --check mode
- Verify formatting without changing files — fails CI if not formatted.
Homework
4 minFormat a whole project with ruff format + import sorting. Add the config to pyproject.toml. Build the one-command check (format-check + lint + tests). Turn on format-on-save in your editor. Submit the config + the check command and confirm it's all green.
Use the Makefile check target + the pyproject black/isort config. The format-on-save setting makes formatting effortless day-to-day.