Ruff for Python: Linter and Formatter, Rule Selection and Configuration

Ruff is a Python linter and code formatter written in Rust and created by the company Astral as an open-source project under the MIT licence. It reimplements the checks of many existing tools, including pycodestyle, pyflakes, isort and dozens of flake8 plugins, and keeps their rule codes, so most of what you know about those tools carries over.
One tool, many rule sets
Before ruff, a typical Python project combined several tools: pycodestyle for layout, pyflakes for likely errors (both usually through flake8), isort for imports, a handful of flake8 plugins, and Black for formatting. Ruff provides equivalents of most of them in one executable, configured in one place. Its main selling point in its documentation is speed, as it is often fast enough to run on every keystroke in an editor.
Each rule has a code made of a prefix and a number, and the prefix tells you where the rule came from. ruff rule E711 prints its documentation, starting with its origin:
# none-comparison (E711)
Derived from the **pycodestyle** linter.
Fix is always available.
## What it does
Checks for comparisons to `None` which are not using the `is` operator.
Rule documentation also explains why the pattern is a problem, usually quoting PEP 8 or the original tool, and gives an example.
Choosing rules
Ruff enables only a subset of its rules by default, and that default set has changed between releases. The reliable approach is to state your selection in configuration:
[tool.ruff]
line-length = 88
target-version = "py312"
[tool.ruff.lint]
select = ["E", "W", "F", "I", "N", "UP", "B"]
ignore = ["E501"]
[tool.ruff.lint.per-file-ignores]
"tests/**" = ["N802"]
A few options do most of the work:
selectreplaces the enabled set;extend-selectadds to it. Prefixes select whole families:"E"is every pycodestyle error ruff implements,"E7"only the statement checks.ignoreremoves rules. Here E501 is ignored because the formatter is trusted with line length.per-file-ignoresrelaxes rules for some files, a common need for tests, migrations or__init__.pyre-exports.target-versiontells ruff the oldest Python version you support, so rules such as pyupgrade's (UP) only suggest syntax that version understands.
To see the full resolved configuration for a file, including which rules are active, run ruff check --show-settings path/to/file.py. Settings can live in pyproject.toml under [tool.ruff], or in a ruff.toml or .ruff.toml file without the tool.ruff prefix.
Preview rules and layout checks
Some rules are marked as preview: new or unstable, and only active when preview mode is enabled with --preview or preview = true. At the time of writing (ruff 0.16), this includes many of pycodestyle's whitespace and blank-line checks, such as E225 and E302. On a file containing def right after an import line and item.price!=None, ruff check --select E,W reported only E401 and E711; with --preview it also reported E302 and E225. The reasoning is that a project using ruff format or Black never sees those problems. If you rely on ruff alone for layout warnings without a formatter, check which rules your version actually runs.
Fixes: safe and unsafe
Many rules come with an automatic fix, marked [*] in the output. Ruff classifies fixes as safe or unsafe. A safe fix preserves the meaning of the code; an unsafe one might change behaviour in some cases, and is applied only with --unsafe-fixes. The E711 fix is an example: replacing x != None with x is not None is almost always right, but would change the result for a class with an unusual __eq__.
The pyupgrade rules show how fixes help with type hints. With target-version = "py310", a function annotated def f(x: Optional[int]) -> List[int]: produced:
up.py:1:1: UP035 `typing.List` is deprecated, use `list` instead
up.py:4:10: UP045 [*] Use `X | None` for type annotations
up.py:4:28: UP006 [*] Use `list` instead of `List` for type annotation
Running ruff check --fix rewrote the annotations to int | None and list[int], the modern forms introduced by PEP 604 and PEP 585.
The formatter
ruff format formats code in a style designed to match Black's, and its documentation lists the known differences. It reads line-length from the shared configuration and has its own [tool.ruff.format] table, for example quote-style = "single" for projects that prefer single quotes, something Black does not offer. Like Black, it supports --check and --diff for CI, and respects the magic trailing comma. The formatter does not sort imports; that is the linter's I rules, run with ruff check --select I --fix. See our guide to Black for the style itself.
Silencing and migrating
# noqa: F401silences one rule on one line, using the same syntax as flake8. Ruff can warn about unusednoqacomments (RUF100).ruff check --add-noqaadds anoqacomment to every current violation, a way to adopt strict rules on old code without fixing everything at once.ruff check --statisticscounts violations per rule, useful for deciding which rules to enable first.
When migrating from flake8, list the plugins you use and find their prefixes in ruff's rules reference; not every plugin has an equivalent, and some rules behave slightly differently. Our comparison of flake8 and pylint describes the tools ruff is most often compared with.
Where it fits
Ruff does not replace a type checker, and it does not implement everything pylint checks. A common modern setup is ruff for linting, import sorting and formatting, plus mypy or another type checker, all run from pre-commit and CI; our guide to checking style locally walks through that setup. For a single snippet, the checker on our home page runs a subset of the same pycodestyle checks in the browser.
Sources
- Ruff documentation (docs.astral.sh/ruff): "Configuring Ruff", "The Ruff Linter" (fix safety, preview), "The Ruff Formatter" (Black compatibility) and the rules reference.
- PEP 8 – Style Guide for Python Code, quoted in ruff's rule documentation.
- pycodestyle documentation, error codes, the origin of ruff's E and W rules.
- Output above produced with ruff 0.16 on Python 3.13.
Linters & formattersCheck style locally
Install a checker in a virtual environment, run it on your project, configure it once and let it fix what it can: pycodestyle, flake8 and ruff step by step.
Linters & formattersBlack, the code formatter
88 characters, double quotes and the magic trailing comma: what Black changes, the few options it has, and how to make it agree with your linter.
Linters & formattersflake8 and pylint compared
Same file, two linters: flake8 found nothing, pylint found six things. What each tool checks, how to configure it and when to use both.