Ruff v0.15.0 is available now! Install it from PyPI, or with your package manager of choice:
uv tool install ruff@latest
As a reminder: Ruff is an extremely fast Python linter and formatter, written in Rust. Ruff can be used to replace Black, Flake8 (plus dozens of plugins), isort, pydocstyle, pyupgrade, and more, all while executing tens or hundreds of times faster than any individual tool.
This release welcomes in sixteen new stable lint rules, six stabilized behaviors for existing lint rules, and support for range suppressions in the linter, as well as the new style guide for the Ruff formatter.
The Ruff 2026 style guide #
The Ruff formatter has seen a host of improvements in the past year, from bug fixes to features to new styles. As always, this is thanks in large part to excellent community contributions, whether it be through submitting issues or opening pull requests.
A few of the most impactful changes include:
- Lambda parameters are now kept on the same line and lambda bodies will be parenthesized to let them break across multiple lines.
- Parentheses around tuples of exceptions in
exceptclauses will now be removed on Python 3.14 and later. - A single empty line is now permitted at the beginning of function bodies.
These are covered in more detail below. For a full description of all changes, see the release notes.
Improved lambda formatting #
Lambda parameter lists are now forced onto the same line as the lambda keyword itself, and the
body of the lambda can now be parenthesized, allowing it to break across multiple lines.
In the 2025 style, the formatter preferred to break long lambda parameter lists across multiple lines over parenthesizing and indenting the body expression, causing the parameters to blend in with the surrounding lines in certain contexts. The 2026 style instead keeps parameters on a single line and prefers to parenthesize the body if necessary:
--- 2025
+++ 2026
def a():
return b(
c,
d,
e,
- f=lambda self,
- *args,
- **kwargs: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs),
+ f=lambda self, *args, **kwargs: (
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs)
+ ),
)The new formatting helps to preserve the separation between the lambda's parameters and other entries in a surrounding list. This change also more closely aligns Ruff with the formatting produced by Black for such expressions.
Simplified except and except* blocks on Python 3.14 #
With the adoption of PEP 758 in Python 3.14, unparenthesized
except and except* blocks are now permitted when catching multiple exceptions. Ruff now enforces
this more concise style in the formatter, as long as your
target-version is 3.14 or later.
--- 2025
+++ 2026
try:
...
-except (A, B, C):
+except A, B, C:
...Preserved empty line at the start of function definitions #
In certain cases it can help legibility to use an empty line to separate a function header or docstring from the body of a function. The 2026 style leaves this up to the user, preserving up to a single blank line in this case.
# Source - remains the same with the 2026 style guide
def some_function():
some_more_code()
# 2025 style guide - removes the blank line
def some_function():
some_more_code()In other formatting news... #
Keep your one-liners #
In previous versions of Ruff, skip directives like # fmt: skip only applied to at most one node.
For example:
--- Source
+++ Ruff v0.14.0
- import this; import that # fmt: skip
+ import this
+ import that # fmt: skip
- def add_one(x): return x+1 # fmt: skip
+ def add_one(x):
return x+1 # fmt: skipThe only way to suppress formatting in these cases was to surround the one-liner with a range
suppression, using fmt: on and fmt: off comments. Of course, this turns the one-liner into a
three-liner, which is not ideal.
Ruff now respects these directives for the entire (logical) line containing the skip directive. This also improves compatibility with Black's treatment of these suppression comments.
Note that this change has been in effect since Ruff v0.14.13 and will not change previously formatted code. However, any one-liners you write going forward should now be preserved.
Check out our preview styles #
At the time of this writing there are only two formatting styles that remain in preview. So now is
an excellent time to turn on preview and let us
know what you think! Both features offer some styling innovations, and would be intentional
deviations from Black. We are eager to refine them in line with feedback from the community.
Fluent layout for method chains (GitHub Discussion): #
This preview style emphasizes the interpretation of a long chain of methods as a sequence of transformations or operations being performed on a specific object of interest. We highlight this by attempting to keep the inferred "object of interest" on the first line, and breaking at each method call, which leads to the following change in practice:
x = (
- df.filter(at_this_very_long > boolean_conditions)
+ df
+ .filter(at_this_very_long > boolean_conditions)
.agg(using_this_long_function)
.merge(other)
)Hugging parentheses with brackets and braces (GitHub Discussion): #
This preview style reduces indentation and vertical space while highlighting the essential content of containers:
def foo_brackets(request):
- return JsonResponse(
- {
- "var_1": foo,
- "var_2": bar,
- }
- )
+ return JsonResponse({
+ "var_1": foo,
+ "var_2": bar,
+ })Introducing linter block suppressions #
In previous versions of Ruff, only two levels of granularity were available for suppressing lint
diagnostics: file-level via ruff: noqa comments, and line-level via inline noqa comments.
As one example, if you wanted to enforce PEP 8 naming conventions in your code, but needed to maintain a single function with arguments that did not follow this style, multiple suppression comments could be needed:
def foo(
legacyArg1, # noqa: N803
legacyArg2, # noqa: N803
legacyArg3, # noqa: N803
legacyArg4, # noqa: N803
): ...Ruff v0.15 stabilizes a new kind of suppression comment for allowing rule violations in blocks of
code. The example above can be rewritten more succinctly with these new ruff: disable and
ruff: enable comments:
# ruff: disable[N803]
def foo(
legacyArg1,
legacyArg2,
legacyArg3,
legacyArg4,
): ...
# ruff: enable[N803]Note that unlike the file- and line-level suppression comments, the bracketed list of codes is required for block-level suppression comments. See the documentation on block suppressions for the full specification.
Also see the newly stabilized lint rules
RUF103 and
RUF104 for validating your
suppression comments automatically.
Rule stabilizations #
The following rules have been stabilized and are no longer in preview:
blocking-http-call-httpx-in-async-function(ASYNC212)blocking-path-method-in-async-function(ASYNC240)blocking-input-in-async-function(ASYNC250)map-without-explicit-strict(B912)if-exp-instead-of-or-operator(FURB110)single-item-membership-test(FURB171)missing-maxsplit-arg(PLC0207)unnecessary-lambda(PLW0108)unnecessary-empty-iterable-within-deque-call(RUF037)in-empty-collection(RUF060)legacy-form-pytest-raises(RUF061)non-octal-permissions(RUF064)invalid-rule-code(RUF102)invalid-suppression-comment(RUF103)unmatched-suppression-comment(RUF104)replace-str-enum(UP042)
Other behavior stabilizations #
This release also stabilizes some additional behavior, previously only available in preview mode:
-
The
--output-formatflag is now respected when running Ruff in--watchmode, and thefulloutput format is now used by default, matching the regular CLI output. -
builtin-attribute-shadowing(A003) now detects the use of shadowed built-in names in additional contexts like decorators, default arguments, and other attribute definitions. -
duplicate-union-member(PYI016) now considerstyping.Optionalwhen searching for duplicate union members. -
split-static-string(SIM905) now offers an autofix when themaxsplitargument is provided, even without asepargument. -
dict-get-with-none-default(SIM910) now applies to more types of key expressions. -
super-call-with-parameters(UP008) now has a safe fix when it will not delete comments. -
unnecessary-default-type-args(UP043) now applies to stub (.pyi) files on Python versions before 3.13.
Other breaking changes #
This release also includes a few miscellaneous breaking changes, mostly related to the release process.
- The
ruff:alpineDocker image is now based on Alpine 3.23 (up from 3.21). - The
ruff:debianandruff:debian-slimDocker images are now based on Debian 13 "Trixie" instead of Debian 12 "Bookworm." - Binaries for the
ppc64(64-bit big-endian PowerPC) architecture are no longer included in our releases. It should still be possible to build Ruff manually for this platform, if needed. - Ruff now resolves all
extended configuration files before falling back on a default Python version.
Thank you! #
Thank you to everyone who provided feedback regarding the changes included in Ruff's preview mode and to our contributors. It's an honor building Ruff with you!
View the full changelog on GitHub.
Read more about Astral — the company behind Ruff.
Thanks to Dhruv Manilawala and Brent Westbrook who contributed to this blog post.
