Learning Goals
3 min- Explain what a linter catches (and what it can't).
- Run
ruff checkand read its findings + rule codes. - Auto-fix with
ruff check --fix. - Configure rules in
pyproject.toml.
Warm-Up · Bugs Without Running
5 minimport os, sys # sys is never used def total(items): sum = 0 # shadows the built-in sum() for itm in items: total += itm # BUG: should be 'sum', not 'total' (NameError at runtime) return sum
This file has three problems a linter spots instantly — without executing it: an unused import, a shadowed built-in, and an undefined-name bug that would crash. Static analysis catches them before your tests even run.
Linting is static testing (Lesson 2): it analyses your code as text. It catches a whole class of bugs — typos, unused/undefined names, unreachable code — for free, instantly, on every save. ruff does it in milliseconds, even on huge projects.
New Concept · ruff in Practice
14 minInstall & run
pip install ruff ruff check . # lint the whole project ruff check myfile.py # lint one file
Reading the output
total.py:1:8: F401 [*] 'sys' imported but unused total.py:5:9: F821 undefined name 'total' total.py:3:5: A001 builtin 'sum' is shadowed [*] = auto-fixable. F401/F821 etc = rule codes (look them up).
F4xx— Pyflakes: real errors (unused imports, undefined names).E/W— pycodestyle: style (spacing, line length).- Hundreds more rule families, all in one fast tool.
Auto-fix the safe ones
ruff check --fix . # fixes the [*] findings automatically ruff check --fix --unsafe-fixes . # also applies riskier fixes (review!)
Configure in pyproject.toml
[tool.ruff] line-length = 100 target-version = "py312" [tool.ruff.lint] select = ["E", "F", "I", "B"] # errors, pyflakes, import-sort, bugbear ignore = ["E501"] # e.g. don't enforce line length here
select chooses which rule families to enable. I even sorts your imports (replacing isort); B (flake8-bugbear) catches likely bugs like mutable default arguments.
Silence a line on purpose
import os # noqa: F401 (intentionally unused — keep the linter quiet)
Use # noqa: <code> sparingly, and always name the specific rule so you don't accidentally hide other issues.
Worked Example · Clean a Messy File
12 min# messy.py — before
import os, json, sys
from collections import OrderedDict
def process(data = []): # mutable default arg — classic bug (B006)
result = OrderedDict() # OrderedDict unneeded in modern Python
for x in data : # extra space before colon
result[x]=x*2 # missing spaces around =
unused = 42 # assigned but never used (F841)
return result$ ruff check messy.py messy.py:1:1: F401 'json' imported but unused messy.py:1:1: F401 'sys' imported but unused messy.py:4:18: B006 mutable default argument messy.py:7:6: E203 whitespace before ':' messy.py:9:5: F841 local variable 'unused' is never used Found 5 errors.
# messy.py — after ruff check --fix + the B006 fix you do by hand import os def process(data=None): # safe default data = data or [] result = {} # plain dict for x in data: result[x] = x * 2 return result
Read the diff
ruff flagged five issues — and auto-fixed the imports and spacing. The one it won't auto-fix (the mutable default data=[], a genuine bug source) it flags with B006 so you fix it deliberately. Run ruff on every file and these whole categories of bug simply stop happening. Next: pylint goes deeper.
Try It Yourself
13 minRun ruff check . on a real project of yours. How many findings? Read the rule codes for the top 3.
Run ruff check --fix .. Review what changed with git diff before committing. Which findings needed manual fixing?
Add a [tool.ruff.lint] config that enables B (bugbear) and I (import sort). Find a real likely-bug it flags in your code.
Mini-Challenge · Zero-Warning Repo
8 minPick a project and get ruff check . to report zero issues — by fixing the real ones and only using a documented noqa (with a reason) where a finding is a deliberate false-positive. Add the ruff config to pyproject.toml so the standard is locked in for the team.
Recap
3 minruff is a blazing-fast linter that statically catches unused/undefined names, likely bugs (bugbear), and style issues — without running your code. ruff check reports, --fix auto-fixes the safe ones, and pyproject.toml configures the rules. It's the cheapest quality win available. Next: pylint for deeper, more opinionated analysis.
Vocabulary Card
- linter
- A tool that statically analyses source for errors and style issues.
- ruff
- An extremely fast Python linter (and formatter) written in Rust.
- rule code
- An identifier like F401 or B006 naming a specific lint rule.
- noqa
- A comment that suppresses a lint finding on a line — use with a reason.
Homework
4 minGet a real project to zero ruff findings: configure the rules in pyproject.toml (enable E, F, I, B), run --fix, fix the rest by hand, document any noqa. Report the before/after counts and one genuine bug ruff caught.
Use the pyproject config from the lesson. The genuine bug is often a B006 mutable-default or an F821 undefined name from a typo.