Two small habits separate clean Python code from messy Python code:
- Formatting — consistent indentation, spacing, line length, quote style
- Linting — flagging code that’s likely a bug or bad practice (unused variables, missing imports, dead code)
Older Python tutorials teach Black for formatting and Flake8 for linting — two separate tools, each with its own config. Modern Python uses Ruff, a single tool that does both jobs and runs about 100 times faster.
Install Ruff
There are two ways to install Ruff:
- As a VS Code extension — already done in the last lesson. This is enough for most of your work.
- As a command-line tool — useful for running it on the whole project, in CI, or before committing code.
We’ll do the command-line install too:
uv tool install ruff
uv tool install installs a command-line tool globally, so you can run ruff from any folder.
Verify it works:
ruff --version
ruff 0.7.0
Format a file
Make hello.py deliberately messy:
def greet( name :str ) ->str :
return f'Hello, {name}'
print(greet( "World" ) )
Run Ruff’s formatter:
ruff format hello.py
1 file reformatted
Open the file. It’s now tidy:
def greet(name: str) -> str:
return f"Hello, {name}"
print(greet("World"))
Ruff fixed the spacing, quote style, and the blank lines between definitions, all by itself.
If you turned on format on save in the last lesson, VS Code does this every time you press save. You’ll rarely run
ruff formatfrom the terminal.
Lint a file
Make hello.py problematic in a different way:
import os
import sys
def greet(name: str) -> str:
return f"Hello, {name}"
unused = 42
greet("World")
Run Ruff’s linter:
ruff check hello.py
hello.py:1:8: F401 [*] `os` imported but unused
hello.py:2:8: F401 [*] `sys` imported but unused
hello.py:9:1: F841 [*] Local variable `unused` is assigned to but never used
Found 3 errors.
[*] 3 fixable with the `--fix` option.
Three issues:
- Two unused imports (
osandsys) - An unused variable (
unused)
Each warning has a code (F401, F841) — Ruff has hundreds of these, each catching a specific bug or smell.
Fix the issues automatically
Many lint warnings can be fixed automatically:
ruff check --fix hello.py
Found 3 errors (3 fixed, 0 remaining).
Open the file:
def greet(name: str) -> str:
return f"Hello, {name}"
greet("World")
The unused imports are gone. The unused variable is gone. Done.
Configuring Ruff
Out of the box, Ruff uses sensible defaults. When you want to tweak them, you add a pyproject.toml file at the root of your project:
[tool.ruff]
line-length = 100
[tool.ruff.lint]
select = ["E", "F", "I", "B", "UP"]
This says:
- Allow lines up to 100 characters (the default is 88)
- Run rule families
E(style),F(pyflakes — likely bugs),I(imports),B(bug patterns),UP(modernise old syntax)
Don’t worry about understanding every rule code yet. As you write more Python, you’ll learn the common ones — and Ruff always explains itself when it complains.
Why bother?
Two reasons.
Readability. Consistent style means you can scan code quickly because nothing looks weird. Every Python project that follows PEP 8 looks like every other one. New contributors don’t have to learn a custom style.
Bug-catching. Many of Ruff’s checks catch real bugs — unused imports often hide a deleted feature; comparing x == None instead of x is None is a common gotcha; mutable default arguments are a classic foot-gun. Ruff catches all of these before your tests do.
The cost is almost zero — Ruff is fast, it integrates with VS Code, and it works without configuration. The benefit is real. Turn it on and never look back.
What’s next
Your editor is configured and your code is being kept tidy. Next, we’ll take a quick look at what really happens when you press Run.