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 except clauses 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: skip

The 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:

Other behavior stabilizations

This release also stabilizes some additional behavior, previously only available in preview mode:

  • The --output-format flag is now respected when running Ruff in --watch mode, and the full output 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 considers typing.Optional when searching for duplicate union members.

  • split-static-string (SIM905) now offers an autofix when the maxsplit argument is provided, even without a sep argument.

  • 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:alpine Docker image is now based on Alpine 3.23 (up from 3.21).
  • The ruff:debian and ruff:debian-slim Docker 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.