Ruff v0.9.0 现已发布!您可以通过 PyPI 或您选择的包管理器进行安装。

uv pip install --upgrade ruff

提醒一下:Ruff 是一个用 Rust 编写的超快速 Python 代码检查器和格式化工具。Ruff 可以替代 Black、Flake8(以及数十个插件)、isort、pydocstyle、pyupgrade 等工具,而且执行速度比任何单个工具快几十甚至几百倍。

新年伊始,Ruff 发布了新的小版本!除其他更改外,此版本还带来了 Ruff 格式化工具的新样式指南、七项代码检查规则的稳定以及两项已稳定的代码检查规则的自动修复。

Ruff 2025 样式指南

此版本为 Ruff 格式化工具带来了新的样式指南。主要更改包括:

  • f-string 中的插值表达式现在会被格式化
  • 现在,在可能的情况下,避免单行隐式拼接字符串
  • 改进了与 Black 样式指南的兼容性

有关所有样式更改的详细列表,请访问发布页面

F-String 格式化

根据新的样式指南,Ruff 现在会格式化 f-string 花括号内的插值表达式。

--- 2024
+++ 2025

     return {
         "statusCode": 200,
-        "body": json.dumps({"message": f"{layer_method()+5}"}),
+        "body": json.dumps({"message": f"{layer_method() + 5}"}),
     }

此外,Ruff 现在将 f-string 格式化为任何其他普通字符串。

  • f-string 的引号现在根据您的项目配置进行规范化。但是,如果规范化会导致无效的 f-string 或需要在 f-string 的字面部分中转义更多引号,则引号将保持不变。
  • 现在,f-string 内部不必要的转义符会被移除。例如,如果 f-string 使用 " 作为其外部引号,\' 将被替换为 '
--- 2024
+++ 2025

  assert (dag_id in actual_found_dag_ids) == should_be_found, (
-     f'dag \'{dag_id}\' should {"" if should_be_found else "not "}'
+     f"dag '{dag_id}' should {'' if should_be_found else 'not '}"
      f"have been found after processing dag '{expected_dag.dag_id}'"
  )

最后,Ruff 现在会检查 f-string 中的插值表达式({} 花括号内的部分),以确定是否可以接受将 f-string 拆分为多行。如果 f-string 中的任何插值表达式包含换行符(无论是使用多行表达式还是括号内的注释),Ruff 现在有时会向 f-string 添加其他换行符,如果这样做有助于将字符串包装到您项目的最大行长

# Source
f"Unsupported serializer {serializer!r}. Expected one of {', '.join(map(repr, _SERIALIZERS))}"

# Add a line break manually to inform Ruff to split the f-string...
f"Unsupported serializer {
    serializer!r}. Expected one of {', '.join(map(repr, _SERIALIZERS))}"

# 2025 style guide: Ruff now splits the f-string as it does not fit the line length limit,
# and it sees from the line break that this would be acceptable
f"Unsupported serializer {serializer!r}. Expected one of {
    ', '.join(map(repr, _SERIALIZERS))
}"

有关 Ruff 如何格式化 f-string 的更多详细信息,请参阅文档

减少单行隐式拼接字符串

隐式拼接字符串可以将长字符串拆分为多个较短的字面量,这是一种方便的方法。然而,它们也容易被忽视,有时会降低可读性,尤其当拼接的字面量并排放在一行时。

根据新的样式指南,如果所有隐式拼接字符串都能放在一行上,Ruff 会将其合并。

--- 2024
+++ 2025

  raise argparse.ArgumentTypeError(
-     f"The value of `--max-history {max_history}` " f"is not a positive integer."
+     f"The value of `--max-history {max_history}` is not a positive integer."
  )

所有隐式拼接字符串都将得到这种处理,除了

  • 三引号字符串 ('''string''')
  • 原始字符串 (r"string")
  • f-string 与包含引号的调试文本或包含引号的格式说明符的一些罕见组合(例如 f'{1=: "abcd \'\'}'f'{x=:a{y:{z:"abcd"}}}'

对于无法自动连接的字符串,Ruff 现在会保留隐式拼接字符串字面量是格式化为多行还是单行。

# Source
single_line = r'^time data "a" doesn\\'t match format "%H:%M:%S". ' f"{PARSING_ERR_MSG}$"
multi_line = (
    r'^time data "a" doesn\\'t match format "%H:%M:%S". '
    f"{PARSING_ERR_MSG}$"
)

# 2024 style guide: the multiline implicitly concatenated string was converted
# to a single-line implicitly concatenated string
single_line = r'^time data "a" doesn\\'t match format "%H:%M:%S". ' f"{PARSING_ERR_MSG}$"
multi_line = r'^time data "a" doesn\\'t match format "%H:%M:%S". ' f"{PARSING_ERR_MSG}$"

# 2025 style guide: neither string can be easily joined
# to remove the implicit concatenation,
# so both are left as they are in the source
single_line = r'^time data "a" doesn\\'t match format "%H:%M:%S". ' f"{PARSING_ERR_MSG}$"
multi_line = (
    r'^time data "a" doesn\\'t match format "%H:%M:%S". '
    f"{PARSING_ERR_MSG}$"
)

保留这些情况的源格式意味着与我们的 ISC001 规则之间的长期存在的格式化工具不兼容问题现已解决。如果用户在 Ruff 配置中同时启用了格式化工具和此代码检查规则,将不再看到警告。

优先折行断言消息

如果一个 assert 语句太长无法放在一行,Ruff 有两种选择。它可以将表达式(语句的第一部分)拆分为多行,也可以将断言消息(逗号后面的语句部分)拆分。

以前,当遇到超过最大行长assert 语句时,Ruff 倾向于拆分 assert 语句的表达式。这样做的好处是通常可以避免在断言消息周围添加额外的括号。然而,Ruff 2025 样式指南更倾向于将断言消息括起来,而不是将断言表达式拆分为多行。我们认为断言表达式是 assert 语句中最重要的部分;在可能的情况下将其保持在单行上优于其他考虑因素,并提高了可读性。

--- 2024
+++ 2025

- assert (match_opt1 is not None) or (
-     match_opt2 is not None
- ), "Cannot find paper abstract section!"
+ assert (match_opt1 is not None) or (match_opt2 is not None), (
+     "Cannot find paper abstract section!"
+ )

这种新的格式化方式与 Ruff 的赋值格式化方式一致,即它倾向于拆分赋值语句右侧的值,然后才诉诸于在左侧对赋值目标使用括号。

减少带括号的返回类型注解

以前,当函数声明超过最大行长时,Ruff 经常在多行或带下标的返回类型注解周围添加括号。现在,如果返回类型自带括号或已知为多行,Ruff 会避免添加括号。

--- 2024
+++ 2025

- def _register_filesystems() -> (
-     Mapping[
-         str,
-         Callable[[str | None, Properties], AbstractFileSystem]
-         | Callable[[str | None], AbstractFileSystem],
-     ]
- ):
+ def _register_filesystems() -> Mapping[
+     str,
+     Callable[[str | None, Properties], AbstractFileSystem]
+     | Callable[[str | None], AbstractFileSystem],
+ ]:
      scheme_to_fs = _BUILTIN_SCHEME_TO_FS.copy()

新的格式化方式现在更接近 Black 的返回类型注解格式。

match case 语句中自动添加 if 守卫的括号

Ruff 以前的版本从不在 match 语句的 case 子句中的 if 守卫添加括号。与此同时,不必要的括号则保持不变。前者可能导致因 case 模式不必要地拆分为多行而难以阅读的格式。后者可能导致不必要地带有大量视觉噪音的带括号的 if 守卫。

Ruff 现在会根据您的项目配置,当 case 行超出其最大允许长度时,自动为 if 守卫添加括号。这样做还有一个额外效果,即 if 守卫会在 case 模式之前断开,从而提高可读性。

--- 2024
+++ 2025

  # Options: line-length = 60

  match command.split():
-     case [
-         "go",
-         direction,
-     ] if direction in current_room.exits:
+     case ["go", direction] if (
+             direction in current_room.exits
+     ):
          current_room = current_room.neighbor(direction)
      case ["go", _]:
          print("Sorry, you can't go that way")
-     case ["wait", _] if (current_room.waiting_room):
+     case ["wait", _] if current_room.waiting_room:
          pass

规则稳定化

以下规则已稳定,不再处于预览模式

其他行为稳定化

以下规则现在具有改进的自动修复行为,这些行为以前仅在预览模式下可用:

最后,Ruff v0.9 稳定了其他几种行为,这些行为以前只对选择加入预览模式的用户可用:

感谢!

感谢所有对 Ruff --preview 模式中包含的格式化工具及其他更改提供反馈的人,尤其要感谢我们的贡献者们。能和你们一起构建 Ruff 是我们的荣幸!


GitHub 上查看完整的变更日志。

了解更多关于 Astral — Ruff 背后的公司。

感谢 Alex Waygood 和 Dhruv Manilawala,他们都为这篇博客文章做出了贡献。