7.3 用Ruff减少格式与静态错误

2026-09-05 15:57 更新

用Ruff减少格式与静态错误

接口测试回答“运行后行为是否正确”,格式化和静态检查回答“代码是否有明显的结构问题”。两者互相补充:Ruff 能发现未使用导入、导入顺序和一部分语法错误,但它不会访问数据库,也不会证明权限分支返回了正确结果。

本节只使用 Ruff 同时完成 lint 和格式检查,不再叠加 Flake8、isort 或 Black。当前核验时 Ruff 最新发布版本为 0.16.6;项目可以先锁定该版本,再按升级计划更新。运行前请用 ruff --version 记录实际版本,版本变化后重新确认规则输出。

安装并核验 Ruff

在项目的开发依赖中加入 Ruff:

python -m pip install "ruff==0.16.6"
ruff --version

预期版本行包含 ruff 0.16.6。如果 shell 找不到 ruff,先确认虚拟环境已经激活;CI 中应使用同一个锁定版本,不要让开发机和流水线自动安装不同版本。

最小配置只打开需要的规则

把下面配置保存到项目根目录的 pyproject.tomlE4E7E9 检查常见代码错误,F 检查 Pyflakes 问题,I 检查导入排序。这里只选一组清楚、容易解释的规则,没有把所有可选规则一次性打开。

[tool.ruff]
target-version = "py311"
line-length = 88

[tool.ruff.lint]
select = ["E4", "E7", "E9", "F", "I"]

target-version 影响 Ruff 允许的 Python 语法判断;它应与项目实际最低 Python 版本一致。line-length 是格式约定,不代表接口字段、数据库列或文章正文必须被截断。

用一个未使用导入验证规则

下面的完整脚本创建临时项目,先检查一个含未使用导入的文件,再检查修正后的文件,最后验证格式检查。它不修改教程目录中的源文件,适合放进本地自检或 CI 的最小示例。

<!-- file: ch07_ruff/ruff_check_demo.py -->

from __future__ import annotations

import shutil
import subprocess
import sys
import tempfile
from pathlib import Path

CONFIG = """\
[tool.ruff]
target-version = "py311"
line-length = 88

[tool.ruff.lint]
select = ["E4", "E7", "E9", "F", "I"]
"""

BAD_SOURCE = """\
import os
import sys


def greet():
    return "ok"
"""

GOOD_SOURCE = """\
def greet() -> str:
    return "ok"
"""


def ruff_command() -> list[str]:
    executable = shutil.which("ruff")
    return [executable] if executable else [sys.executable, "-m", "ruff"]


def run_ruff(project: Path, *arguments: str) -> subprocess.CompletedProcess[str]:
    return subprocess.run(
        [*ruff_command(), *arguments],
        cwd=project,
        text=True,
        capture_output=True,
        check=False,
    )


def self_check() -> None:
    with tempfile.TemporaryDirectory(prefix="ruff-demo-") as temporary:
        project = Path(temporary)
        (project / "pyproject.toml").write_text(CONFIG, encoding="utf-8")
        bad = project / "bad_imports.py"
        good = project / "good.py"

        bad.write_text(BAD_SOURCE, encoding="utf-8")
        lint_failed = run_ruff(project, "check", "bad_imports.py")
        assert lint_failed.returncode != 0
        assert "F401" in lint_failed.stdout + lint_failed.stderr

        good.write_text(GOOD_SOURCE, encoding="utf-8")
        lint_passed = run_ruff(project, "check", "good.py")
        assert lint_passed.returncode == 0, lint_passed.stdout + lint_passed.stderr

        format_passed = run_ruff(project, "format", "--check", "good.py")
        assert format_passed.returncode == 0, (
            format_passed.stdout + format_passed.stderr
        )

    print("ruff check self-check passed")


if __name__ == "__main__":
    self_check()

在包含 ch07_ruff/ 的目录运行:

python ch07_ruff/ruff_check_demo.py

预期输出为 ruff check self-check passed。第一次 ruff check bad_imports.py 预期失败,脚本把它当成被验证的行为;修正后的文件才要求返回码为 0。只检查进程成功退出,不检查具体规则号,会把“Ruff 运行了但没有启用 F 规则”的配置错误漏掉,所以示例明确断言 F401

区分检查模式和修复模式

日常检查可以使用:

ruff check .
ruff format --check .

前者报告静态规则问题,后者只判断格式是否已经符合 Ruff,不改动文件。需要让 Ruff 修改代码时才使用:

ruff check --fix .
ruff format .

修复命令会写文件,应在查看差异、运行测试和确认规则范围后执行。不要把 --fix 放进一个开发者不易发现的提交钩子里,也不要用“格式检查通过”代替行为测试。对有副作用的导入、数据库迁移和安全逻辑,自动修复后仍需人工阅读差异。

导入排序规则 I 能处理常见的标准库、第三方和本地导入顺序,但它不理解所有项目边界。若项目使用特殊的命名空间或动态导入,应为相关路径配置 extend-per-file-ignores,并保留一次真实导入测试。配置越宽松,CI 的成功含义越窄;配置越严格,也要给团队提供清楚的修复方式。

Ruff 不负责什么

Ruff 的 lint 和 format 不会替代类型检查,也不会执行 FastAPI 应用、查询 PostgreSQL 或验证 HTTP 响应。一次交付检查至少应把它和以下结果分开记录:

  • 静态检查:ruff check .ruff format --check .
  • 接口测试:pytest 或 TestClient/AsyncClient 测试的状态码、字段和失败路径。
  • 数据库验证:专用 PostgreSQL 上的迁移、约束、事务和查询。

如果 Ruff 通过但异步接口测试失败,问题仍然存在;如果接口测试通过但 Ruff 报 F401,也应修正代码或明确说明忽略理由。把不同检查合并成一个“全绿”数字,会让失败定位变慢。

本节实际核验了规则配置、未使用导入发现、格式检查和 Ruff 版本输出,没有把静态分析描述成类型安全或运行时正确性证明。

资料来源

以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号