Python Tutorial: Ruff - A Fast Linter & Formatter to Replace Multiple Tools and Improve Code Quality
Based on Corey Schafer's video on YouTube. If you like this content, support the original creators by watching, liking and subscribing to their content.
Ruff is an all-in-one Python linter and formatter that can replace multiple separate tools (linting, formatting, import sorting, and modernization) with one fast workflow.
Briefing
Ruff is positioned as an all-in-one, Rust-based Python linter and formatter that can replace a patchwork of tools—while running fast enough to make continuous feedback practical. Built by Astral (the same team behind the UV package manager), Ruff consolidates linting, formatting, import sorting, and code modernization into a single workflow, and it can automatically fix many issues without changing program behavior. That combination—speed plus consolidation—matters because it reduces tool sprawl and makes it easier to keep code consistent across projects.
The walkthrough starts with installation options. Ruff can be installed per-project into a virtual environment via pip, but that limits availability to each environment. For global use, pipx is presented as a common approach: pipx installs Python packages into isolated environments while exposing their command-line interfaces globally. Homebrew is also mentioned as an alternative, though the preference is to avoid Homebrew-managed Python packages for dependency cleanliness. The tutorial’s preferred method uses UV to install Ruff as a global tool (via `uv tool install rough`), followed by a quick verification using `ruff --version`.
From there, Ruff’s command-line workflow is demonstrated. Running `ruff check main.py` reports a set of errors with line numbers and error codes, and it distinguishes which problems are fixable. In the example script, Ruff flags issues such as multiple imports on one line, unused imports (e.g., `cis`, `time`, `datetime`, `random`), and a discouraged exception pattern: “do not use bare except” (E722). The error code becomes a learning path: Ruff’s documentation explains why the rule exists—such as how `except BaseException`-like behavior can interfere with interrupts like Ctrl+C and can mask other problems—and provides safer alternatives like catching more specific exceptions.
Ruff’s autofix capability is then put to work. Using `ruff check --fix` applies only safe fixes (unused imports and similar issues), leaving the non-automatable bare-except problem for manual correction. To preview changes without modifying files, the tutorial uses `--diff`, showing a Git-style diff of what would be altered. Formatting is handled separately with `ruff format`, and the recommended workflow is to run `ruff check --fix` first, then `ruff format`, so lint-driven edits don’t fight formatting.
For continuous feedback, Ruff’s `--watch` mode is highlighted: it monitors files and re-lints on save, immediately surfacing new issues like unused imports. Configuration is handled through `pyproject.toml` (or `ruff.toml`), with Ruff settings nested under `tool.ruff`. The tutorial shows extending the default rule set via `lint.extend-select`, using rule codes such as `PTH` to enforce `pathlib` usage over built-in `open`. It also covers selecting all rules (`lint.extend-select = [
Cornell Notes
Ruff is a fast, Rust-based Python linter and formatter that consolidates multiple common tools into one. It reports issues with precise line numbers and error codes, and it can automatically fix many safe problems using `ruff check --fix` while leaving riskier cases for manual review. Ruff also supports formatting (`ruff format`) and real-time linting (`ruff check --watch`). Configuration lives in `pyproject.toml` under `tool.ruff`, where rule sets can be extended (e.g., `PTH` for `pathlib`) and specific error codes can be ignored. This matters because it reduces tool sprawl, speeds up feedback loops, and makes code consistency enforceable across projects.
How does Ruff’s command-line workflow help developers learn while fixing code?
What’s the practical difference between `ruff check --fix` and `ruff check --diff`?
Why is the order “lint fixes first, then formatting” recommended?
How does Ruff’s `--watch` mode change day-to-day usage?
How do `pyproject.toml` rule selections like `lint.extend-select` affect what Ruff flags?
What’s the purpose of ignoring specific error codes in Ruff configuration?
Review Questions
- When Ruff reports an error code like E722, what workflow should a developer follow to understand and fix it correctly?
- What kinds of issues are more likely to remain after running `ruff check --fix`, and why might Ruff avoid auto-fixing them?
- How would you structure a `tool.ruff` configuration in `pyproject.toml` to both extend rules (e.g., PTH) and ignore a specific rule code?
Key Points
- 1
Ruff is an all-in-one Python linter and formatter that can replace multiple separate tools (linting, formatting, import sorting, and modernization) with one fast workflow.
- 2
Ruff can be installed per-project with pip, globally with pipx, or globally with UV; verify with `ruff --version`.
- 3
Use `ruff check` for linting and `ruff format` for formatting; a common workflow is `ruff check --fix` first, then `ruff format`.
- 4
Ruff’s `--fix` applies only safe, behavior-preserving changes, while `--diff` previews edits without modifying files.
- 5
Ruff supports real-time feedback via `ruff check --watch`, re-linting on file save.
- 6
Ruff configuration belongs in `pyproject.toml` under `tool.ruff`, where rule sets can be extended with `lint.extend-select` and noise can be reduced with `lint.ignore`.
- 7
Ruff’s type-hinting rules are not the same as a type checker; pair Ruff with a type checker like mypy for deeper correctness checks.