Skip to content

Setting the 2026 stable style #4875

@cobaltt7

Description

@cobaltt7

2023: #3407
2024: #4042
2025: #4522
Ruff: astral-sh/ruff#20482

As always, the ideal is for all preview features to be stabilized, not including the unstable ones.


Ordered by most controversial to least:

multiline_string_handling (#1879) Make expressions involving multiline strings more compact.

Input

textwrap.dedent(
    """\
    This is a
    multiline string
"""
)

MULTILINE = """
foobar
""".replace(
    "\n", ""
)

Output (off)
unchanged

Output (on)

textwrap.dedent("""\
    This is a
    multiline string
""")

MULTILINE = """
foobar
""".replace("\n", "")

Fairly controversial. There were some issues in #4159 that were never fully fixed. Additionally, it's only been in preview since v25.11 (but the issues were resolved as-best-as-possible since v25.9).
However, we haven't gotten any further feedback since then. I think the current state is an improvement overall and we probably won't be able to do much else.
IMO, we should at least include it in the first v26 beta to get user feedback, even if we don't plan on stabilizing it.

wrap_long_dict_values_in_parens (#3440) Add parentheses around long values in dictionaries. Deferred to next year

Input

my_dict = {
    "a key in my dict": a_very_long_variable * and_a_very_long_function_call() / 100000.0
}

Output (off)
unchanged

Output (on)

my_dict = {
    "a key in my dict": (
        a_very_long_variable * and_a_very_long_function_call() / 100000.0
    )
}

Could cause a lot of churn. Adds more parentheses and lines to reduce line length. Deferred in 2023 and 2024, then moved to unstable until 25.1, when the issues with it were resolved and it was moved to preview. Ruff has already decided this to be a non-priority (astral-sh/ruff#12856, #4123).

wrap_comprehension_in (#4699) Wrap the in clause of list and dictionary comprehensions across lines if it would otherwise exceed the maximum line length. Deferred to next year

Input

[a for graph_path_expression in refined_constraint.condition_as_predicate.variables]

Output (off)

[
    a
    for graph_path_expression in refined_constraint.condition_as_predicate.variables
]

Output (on)

[
    a
    for graph_path_expression in (
        refined_constraint.condition_as_predicate.variables
    )
]

Could cause a lot of churn. Adds more parentheses and lines to reduce line length. Ruff has already decided this to be a non-priority (#4123).

remove_parens_from_assignment_lhs (#4865) Remove unnecessary parentheses from the left-hand side of assignments while preserving magic trailing commas and intentional multiline formatting.

Input

(c, *_) = a()

Output (off)
unchanged

Output (on)

c, *_ = a()

An unobjective change, but can cause lots of churn, and was added very recently (Not released yet, will hopefully be in a 25.12).

fix_type_expansion_split (#4777) Fix type expansions split in generic functions.

Input

def func1[T: (int, str)](a,): ...

Output (off)

def func1[
    T: (int, str)
](a,): ...

Output (on)

def func1[T: (int, str)](
    a,
): ...

Causes a good amount of changes, but should be fairly objective.

standardize_type_comments (#4645) Format type comments which have zero or more spaces between # and type: or between type: and value to # type: (value).

Input

#       type:     ignore

Output (off)
unchanged

Output (on)

# type: ignore

Causes changes, but they are minimal, localized, and objective.

always_one_newline_after_import (#4489) Always force one blank line after import statements, except when the line after the import is a comment or an import statement.

Input

from middleman.authentication import validate_oauth_token


logger = logging.getLogger(__name__)

Output (off)
unchanged

Output (on)

from middleman.authentication import validate_oauth_token

logger = logging.getLogger(__name__)

Only changes one part of each file, and changes are minimal and objective. Was deferred in 2025, just because it was new.

remove_parens_around_except_types (#4720) Remove parentheses around multiple exception types in except and except* without as. See PEP 758 for details.

Input

try:
    ...
except (A, B, C):
    ...

Output (off)
unchanged

Output (on)

try:
    ...
except A, B, C:
    ...

New feature introduced in & gated to Python 3.14. No changes in most codebases, which are under 3.14. When there are changes, they're minimal, localized, and objective.

fix_module_docstring_detection (#4764) Fix module docstrings being treated as normal strings if preceded by comments.

Input

# comment
"""
docstring
"""
from __future__ import annotations

Output (off)
unchanged

Output (on)

# comment
"""
docstring
"""


from __future__ import annotations

Bug fix; only changes one thing per file at max.

normalize_cr_newlines (#4710) Add \r style newlines to the potential newlines to normalize file newlines both from and to.

Input

a[CR]
b[LF]
c[CR][LF]
#1[CR][LF]

Output (off)

a[CR]
b[LF]
c[CR]
# 1[CR]

Output (on)

a[CR]
b[CR]
c[CR]
# 1[CR]

Bug fix, was only ever found by Fuzz and shouldn't cause any noticable source code changes in practice.

fix_fmt_skip_in_one_liners (#4800) Fix # fmt: skip behavior on one-liner declarations, such as def foo(): return "mock" # fmt: skip, where previously the declaration would have been incorrectly collapsed.

Input

if True: print("this"); print("that") # fmt: skip

Output (off)

if True:
    print("this"); print("that") # fmt: skip

Output (on)
unchanged

Purely a bug fix, shouldn't actually change any pre-formatted code.


Also, we should remember to regenerate _width_table.py for v26 (#4253).

IMO, everything fix_type_expansion_split and below should definitely be stabilized. I see arguments either way for the items above it. Discussion welcome!

Metadata

Metadata

Assignees

No one assigned

    Labels

    C: maintenanceRelated to project maintenance, e.g. CI, testing, policy changes, releasesT: enhancementNew feature or requestT: styleWhat do we want Blackened code to look like?

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions