7.3 用Ruff减少格式与静态错误
用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.toml。E4、E7、E9 检查常见代码错误,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 版本输出,没有把静态分析描述成类型安全或运行时正确性证明。
资料来源
- 主要参考:fastapi-best-practices 中文 README 的代码质量与格式化主题。本节将原文的格式检查建议收敛为一套 Ruff 配置和可执行检查。
- 官方文档:Ruff 配置、Ruff lint、Ruff formatter、Ruff 发布记录。

免费 AI IDE


更多建议: