小白学 Python
课程GitHub
© 2026 小白学 Python · 基于 walter201230/Python 教程
课程目录GitHub
Python 环境加载中…

pyproject.toml + uv

本章为概念阅读 · 预计 20 分钟

教学 01 / 06· 已读

pyproject.toml + uv:彻底告别「我电脑能跑你电脑跑不了」

这一章是纯阅读章节,没有练习题。

uv、pip、虚拟环境这些都是命令行工具,浏览器里的 Pyodide 跑不起来。这一章我们把概念、命令、最佳实践讲清楚,各位在自己本地装好 Python 环境之后实际操作一遍就能熟练。

各位有没有过这种崩溃时刻——把代码打包发给同事,同事跑了一下,报一堆 ModuleNotFoundError。「pip install 一下就好了。」「装哪个版本?」翻了翻自己的电脑,半天没找到一份完整的 requirements.txt,最后只好憋出一句:「呃,我电脑能跑啊,奇了怪了。」

这种事在 Python 圈子里实在太常见了。直到 2018 年 PEP 518 出台,2021 年 PEP 621 跟进,社区才终于约定:所有项目元数据、依赖、工具配置,统一塞进一个文件,叫 pyproject.toml。

到了 2024 年,又出了一个叫 uv 的工具,Astral 出品(就是写 ruff 那家),用 Rust 写的,比 pip 快 10 到 100 倍,单二进制,没有任何依赖,一条命令装上就能用。

这两件武器加在一起,就是这一章要讲的内容。学完之后,开新项目从零到「一个能跑、能锁定依赖、能跑测试的项目」只需要四五条命令。

老办法到底惨在哪

先回忆一下老办法长什么样。一个「正经的」Python 项目,目录里通常有这些文件:

my-project/
├── setup.py
├── setup.cfg
├── requirements.txt
├── requirements-dev.txt
├── MANIFEST.in
├── pytest.ini
├── .flake8
├── tox.ini
└── my_project/
    └── __init__.py

光配置文件就八九个。每个文件管的事都不一样:

  • setup.py:打包用,告诉 pip 这个项目的名字、版本、入口
  • setup.cfg:setup.py 的一部分配置可以挪进来
  • requirements.txt:生产依赖列表
  • requirements-dev.txt:开发依赖列表(测试、linter、formatter)
  • MANIFEST.in:打包时要带上哪些非代码文件
  • pytest.ini:pytest 的配置
  • .flake8:flake8 的配置
  • tox.ini:多版本测试用

而最惨的是:requirements.txt 不锁版本。

requests
flask
sqlalchemy

干干净净,三行搞定。半年后某天同事拉下来跑,requests 自动装了最新版,结果有个废弃的 API 被删了,代码挂掉。这就是著名的「能跑」和「能复现」之间的鸿沟。

到了 2024 年,社区终于把场子收拾干净了:

  • 元数据格式 统一成 pyproject.toml(PEP 621)
  • 依赖格式 统一成 PEP 508
  • 工具 推荐用 uv(速度王者)

往下我们就一步步看,新办法到底有多省心。

教学 02 / 06· 已读

pyproject.toml 是个啥

pyproject.toml 是一个文件名,文件格式是 TOML。各位没接触过 TOML 的童鞋别紧张,它就是个比 JSON 友好、比 YAML 严格的配置格式。长这样:

[project]
name = "my-project"
version = "0.1.0"

中括号 [project] 是「段」,下面 key = value 是配置项。字符串用双引号,数字直接写,列表用方括号,跟大多数语言的语法差不多。

pyproject.toml 的核心思想是:项目的所有信息,集中放一个文件。具体能放什么?看下面这个完整例子:

[project]
name = "my-project"
version = "0.1.0"
description = "两点水的小工具"
requires-python = ">=3.10"
dependencies = [
    "httpx>=0.27",
    "click>=8.0",
]

[project.optional-dependencies]
dev = [
    "pytest>=8.0",
    "ruff>=0.5",
]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.ruff]
line-length = 100

[tool.pytest.ini_options]
testpaths = ["tests"]

各位数一下,这一个文件里同时承担了多少职责:

  • [project] 段:项目名字、版本、Python 版本要求、依赖列表——以前这些写在 setup.py 里
  • [project.optional-dependencies] 段:开发依赖、可选依赖——以前是 requirements-dev.txt
  • [build-system] 段:怎么打包这个项目——以前是 setup.py + MANIFEST.in
  • [tool.ruff] 段:ruff 的配置——以前是 .ruff.toml 或 setup.cfg
  • [tool.pytest.ini_options] 段:pytest 的配置——以前是 pytest.ini

一个文件搞定一切。新人接手一个项目,打开 pyproject.toml,从上到下扫一遍,整个项目的元数据、依赖、工具配置全在脑子里了。

最简 pyproject.toml 逐字段拆解

下面这段是一个项目能跑起来的最小集合:

[project]
name = "my-project"
version = "0.1.0"
requires-python = ">=3.10"
dependencies = [
    "httpx>=0.27",
]

五个字段,每个都讲清楚。

name

name = "my-project"

项目的名字。这个名字是发布到 PyPI 时用的,全网唯一。命名规则:

  • 只能包含字母、数字、连字符 -、下划线 _、点 .
  • 不区分大小写
  • 习惯上用全小写 + 连字符

注意:项目名(name)和包导入名(import 用的)不一定相同。比如著名的 Pillow,项目名是 Pillow,但导入时是 import PIL;scikit-learn 项目名带连字符,导入时是 import sklearn。

version

version = "0.1.0"

版本号。建议遵守语义化版本(Semantic Versioning),格式是 MAJOR.MINOR.PATCH:

  • MAJOR:大改动、不兼容更新
  • MINOR:新加功能,向后兼容
  • PATCH:修 bug,向后兼容

新项目从 0.1.0 起步,第一个稳定版打 1.0.0。

requires-python

requires-python = ">=3.10"

声明这个项目需要哪个版本的 Python。强烈建议都写上这一行,原因有三:

  1. 有人用 Python 3.7 装你的包,能立刻报错,而不是跑到一半才挂
  2. pip 在解析依赖时会用这个信息选合适的子依赖
  3. 工具(ruff、mypy)会用这个信息决定哪些语法可用

版本范围语法用的是 PEP 440:

  • >=3.10:3.10 或更高都行
  • >=3.10,<4.0:3.10 起,但 4.0 之前
  • ~=3.10:3.10.x 系列,不允许 3.11
  • ==3.10.*:3.10 的任意小版本

2026 年开新项目,建议直接写 >=3.10 或 >=3.11。3.9 已经接近退役。

dependencies

dependencies = [
    "httpx>=0.27",
    "click>=8.0",
]

项目运行时需要的依赖列表。每一项是一个字符串,遵守 PEP 508 语法。常见的几种写法:

dependencies = [
    "httpx",                   # 任意版本
    "httpx>=0.27",             # 0.27 起
    "httpx>=0.27,<1.0",        # 范围
    "httpx==0.27.2",           # 钉死
    "httpx[http2]",            # 带 extras
    "httpx ; python_version >= '3.10'",  # 条件依赖
]

各位平时最常用的是 >=X.Y 这种「下限」写法。这是社区惯例:写下限,别写上限,除非确实知道某个上限会出问题。原因是:你今天写了 httpx<1.0,明天 httpx 1.0 出来了,所有依赖你的项目都被卡住——这个就叫「上限污染」,是个流毒。

依赖的「精确版本」靠 lock 文件(uv.lock)来记录,下面会讲。

一个完整的最小例子

把这五个字段拼起来,一个能跑的最小 pyproject.toml:

[project]
name = "my-project"
version = "0.1.0"
requires-python = ">=3.10"
dependencies = [
    "httpx>=0.27",
]

就这。五行,比 setup.py 短得多。新人看一眼就懂。

教学 03 / 06· 已读

uv 是什么 & 怎么装

讲完文件格式,现在轮到工具了。

uv 是 Astral 出的 Python 包管理器和项目管理器。Astral 这家公司各位应该不陌生,他们做的 ruff 现在是 Python linter 兼 formatter 的事实标准。uv 是他们的下一款产品,目标是替代 pip、pip-tools、pipenv、poetry、virtualenv、pyenv 一整套老工具。

它的卖点:

  • 快:用 Rust 写的,依赖解析比 pip 快 10-100 倍。装个 numpy 半秒搞定
  • 单二进制:uv 本身没有 Python 依赖,一个可执行文件,往哪里一放就能用
  • 统一:项目管理(创建项目、加依赖、跑脚本)、Python 版本管理(装 Python 解释器)、虚拟环境管理(venv),全在一个工具里
  • 兼容:uv pip install 跟 pip install 用法一致;pyproject.toml 用的是 PEP 621 标准,迁出迁入没壁垒

2024 年初发布以来,uv 已经被各大公司、开源项目快速采用。2026 年的现在,开新项目首选 uv,几乎没有疑问。

安装 uv

macOS 用户最简单:

brew install uv

跨平台通用方案:

curl -LsSf https://astral.sh/uv/install.sh | sh

Windows 用户用 PowerShell:

powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

如果各位电脑里已经有 pip:

pip install uv

但更推荐独立安装——uv 本来就是为了脱离 pip 而设计的,没必要绑着 pip 装。

装完之后看一下版本:

uv --version

输出大概是:

uv 0.5.20

顺便:装 Python 本身

很多童鞋的电脑里没有合适版本的 Python,或者只有系统自带的 Python 3.9。uv 还能帮各位装 Python:

uv python install 3.12

跑完之后,Python 3.12 就装到 uv 管理的目录里了。这相当于一个轻量版的 pyenv。

看看装了哪些 Python:

uv python list

输出大致:

cpython-3.12.7-macos-aarch64-none           /Users/foo/.local/share/uv/python/...
cpython-3.11.10-macos-aarch64-none          /Users/foo/.local/share/uv/python/...
cpython-3.10.15-macos-aarch64-none          /Users/foo/.local/share/uv/python/...

以后开新项目直接挑想用的版本,不用纠结电脑里装了什么。

uv init 创建一个项目

理论讲够了,开干:

uv init my-project

这一条命令做了一堆事。看看生成的目录:

cd my-project
ls -la

输出大致:

.git/
.gitignore
.python-version
README.md
main.py
pyproject.toml

逐个文件看一下。

.python-version

3.12

这一个文件钉死了项目用的 Python 版本。各位到任何一台装了 uv 的电脑上,进入这个目录,uv 会自动用 3.12,没装就自动装。

pyproject.toml

[project]
name = "my-project"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = []

跟我们前面手写的最小例子很像,只多了 description 和 readme。

main.py

python
def main():
    print("Hello from my-project!")


if __name__ == "__main__":
    main()

一个可以直接跑的「Hello World」入口。

跑一下试试

uv run main.py

输出:

Hello from my-project!

uv run 这个命令是 uv 的核心入口之一,它会做这几件事:

  1. 检查项目的 Python 版本是否合适,没有就自动装
  2. 检查 .venv/ 虚拟环境是否存在,不存在就自动建
  3. 检查依赖是否齐全,不齐全就自动装
  4. 用项目的虚拟环境跑命令

所以从 uv init 到代码跑起来,两条命令:uv init my-project、uv run main.py。中间没有任何「先创建虚拟环境、再激活、再装依赖」的步骤。这就是 uv 的体验。

教学 04 / 06· 已读

uv add / uv run:日常工作流

新项目跑起来了,现在加点东西进去。

uv add 添加依赖

老办法:

pip install httpx
# 然后手动打开 requirements.txt,加一行
echo "httpx" >> requirements.txt

两步走,而且很容易忘记第二步。uv 的办法:

uv add httpx

一条命令搞定。这条命令背后做了什么?

  1. 解析 httpx 的最新版本(满足项目 requires-python 的)
  2. 装到项目的 .venv/ 里
  3. 把 httpx>=0.28 写到 pyproject.toml 的 dependencies 里
  4. 更新 uv.lock,把 httpx 和它所有传递依赖的精确版本都钉下来

打开 pyproject.toml 看看:

[project]
name = "my-project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = [
    "httpx>=0.28",
]

dependencies 多了一行,自动添加。各位不需要手动维护这个列表。

加多个依赖

uv add httpx click rich

一次加三个,依赖解析一起跑,比一个一个 pip install 快得多。

加开发依赖

测试用的 pytest、linter 用的 ruff,这些不是项目运行时需要的,只在开发时用,应该放在「开发依赖」里。uv 用 --dev 标志:

uv add --dev pytest ruff

这次 pyproject.toml 多了一段:

[dependency-groups]
dev = [
    "pytest>=8.3",
    "ruff>=0.8",
]

注意是 [dependency-groups] 而不是 [project.optional-dependencies]——这是 PEP 735 的新格式,uv 默认使用。

删除依赖

uv remove rich

不光从 pyproject.toml 删除,还会从 .venv/ 卸载,并且更新 uv.lock。删得干净。

升级依赖

uv add httpx --upgrade

或者:

uv lock --upgrade-package httpx

升级到最新满足约束的版本。

指定版本范围

如果对版本有特殊要求:

uv add "httpx>=0.27,<0.30"

uv run 跑命令

uv run 不光能跑 main.py,能跑任何命令。

跑一段 Python 脚本:

uv run python -c "import httpx; print(httpx.__version__)"

跑 pytest:

uv run pytest

跑自定义脚本:

uv run python scripts/build.py

uv run 的好处是:它保证用的是项目的虚拟环境。各位不用 source .venv/bin/activate,不用每次新开终端都重新激活,进入项目目录之后直接 uv run 就行。

「那如果我就是想激活一下,跑一堆命令呢?」当然也可以:

source .venv/bin/activate
python -c "import httpx; print(httpx.__version__)"
pytest

激活之后跟传统流程一样。

跑命令行工具

很多包会装一个命令行工具,比如 ruff 装完会有 ruff 这个命令。在 uv 项目里这样跑:

uv run ruff check .

uv 会从 .venv/bin/ 里找 ruff 来执行。

跑临时一次性命令

有时候各位想用一个工具,但不想把它加进项目依赖:

uvx httpie https://httpbin.org/get

uvx 是 uv tool run 的简写,它会在一个临时环境装上 httpie,跑完即丢,不污染项目。这相当于 pipx run,但快得多。

uv tool install:装 CLI 工具

想全局装 ruff,让任何目录都能用?

uv tool install ruff

跟 pipx install ruff 一样,但快得多。装完之后 ruff 就在 PATH 里了。

升级:

uv tool upgrade ruff

卸载:

uv tool uninstall ruff

各位可以拿 uv tool 替代 pipx、brew install 一些 Python CLI 工具。

教学 05 / 06· 已读

uv sync:环境字节级一致

讲到这里,关键问题来了:同事拉下来怎么跑?

老办法:

git clone <repo>
cd <repo>
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
pip install -r requirements-dev.txt

五条命令。跨平台还有差异,Windows 激活虚拟环境的命令不一样。

uv 的办法:

git clone <repo>
cd <repo>
uv sync

完事。uv sync 会做:

  1. 检查 .python-version 指定的 Python 版本,没有就装
  2. 创建 .venv/(如果不存在)
  3. 按 uv.lock 里钉死的精确版本装所有依赖(包括开发依赖)

「精确版本」是关键。uv.lock 里记录的不是 httpx>=0.28,而是 httpx==0.28.1、加上 httpx 的所有传递依赖也都钉到具体版本。所以 uv sync 出来的环境,跟开发者电脑上的环境,字节级一模一样。

uv.lock 长什么样

head -30 uv.lock

输出大致:

version = 1
revision = 1
requires-python = ">=3.12"

[[package]]
name = "anyio"
version = "4.6.2.post1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
    { name = "idna" },
    { name = "sniffio" },
]
sdist = { url = "https://files.pythonhosted.org/packages/.../anyio-4.6.2.post1.tar.gz", hash = "sha256:..." }

每个包记录了:

  • 名字、版本
  • 来源(PyPI、git、本地路径……)
  • 它依赖了哪些其他包
  • 源代码包(sdist)的下载地址、哈希值
  • 二进制轮子(wheel)的下载地址、哈希值

哈希值是关键——uv sync 装的时候会校验,确保下载到的包跟 lock 时一模一样,避免了「PyPI 上的包被人篡改」这种供应链攻击。

几个常用 sync 选项

只装生产依赖,不装开发依赖(部署时常用):

uv sync --no-dev

强制重新解析依赖(比如改了 pyproject.toml 之后):

uv sync --reinstall

只装某个 dependency-group:

uv sync --only-group dev

各位平常 90% 的时间都只用 uv sync 不带任何参数,够了。

自动管理虚拟环境

老办法的虚拟环境流程,各位都熟:

python -m venv .venv
source .venv/bin/activate    # macOS/Linux
.venv\Scripts\activate       # Windows
pip install ...

这一套有几个痛点:

  1. 容易忘记激活
  2. 跨平台不统一
  3. 切项目要先 deactivate 再激活新的
  4. 用错虚拟环境是常见 bug

uv 的策略:根本不需要激活。每次跑命令用 uv run,uv 会自动找到当前目录的 .venv/,用里面的 Python 跑。

要看当前用的是哪个 Python:

uv run which python

输出:

/Users/walter/projects/my-project/.venv/bin/python

实战:从零到「能跑、能锁、能测」

需求:写一个「抓取一个 URL,打印响应状态码」的小工具,要求:

  1. 用 httpx 发请求
  2. 用 pytest 写一个测试
  3. 锁定依赖,发给同事能直接跑

全套命令长这样:

# 1. 创建项目
uv init url-checker
cd url-checker

# 2. 加生产依赖
uv add httpx

# 3. 加开发依赖
uv add --dev pytest

# 4. 写代码

# 5. 跑测试
uv run pytest

# 6. 提交到 git
git add .
git commit -m "init project"

主逻辑文件 url_checker.py:

python
import httpx


def check_url(url: str) -> int:
    """返回 URL 的响应状态码"""
    response = httpx.get(url, timeout=5.0)
    return response.status_code


def main():
    url = "https://httpbin.org/status/200"
    code = check_url(url)
    print(f"{url} -> {code}")


if __name__ == "__main__":
    main()

发给同事:

git push origin main

同事拉下来:

git clone <repo>
cd url-checker
uv sync
uv run url_checker.py

三条命令,环境完全一致,能跑。这就是 2026 年的工作流。

项目最终的目录结构

url-checker/
├── .git/
├── .gitignore
├── .python-version
├── .venv/              # uv 自动生成,不进 git
├── README.md
├── pyproject.toml
├── tests/
│   └── test_url_checker.py
├── url_checker.py
└── uv.lock

进 git 的有:

  • .gitignore
  • .python-version
  • README.md
  • pyproject.toml
  • uv.lock(很重要,别忘了提交)
  • tests/
  • url_checker.py

不进 git 的:

  • .venv/
  • __pycache__/、*.pyc

各位常犯的错是:忘了提交 uv.lock。没有 lock 文件,uv sync 没法保证版本一致。所以记住:uv.lock 是项目的一部分,必须进 git。

教学 06 / 06

uv vs pip vs poetry vs pipenv

各位经常听人说哪个工具好哪个工具坏。来个不带感情色彩的对比:

工具速度锁定文件项目管理Python 管理2026 推荐度
pip慢没有(要配 pip-tools)没有没有老项目维护用
pipenv极慢Pipfile.lock有没有不推荐
poetry中等poetry.lock有没有还能用
uv极快uv.lock有有首选

如果是 2026 年开新项目,无脑选 uv;如果是老项目还在用 pip + requirements.txt,建议尽快迁过来;老项目用了 poetry 也别慌,能跑就让它跑,等下次大重构再说。

从 requirements.txt 迁移过来

各位手上有老项目,已经在用 requirements.txt,怎么迁过来?

场景一:从 requirements.txt 直接 lock

如果各位的 requirements.txt 已经是「输入约束」(写了 httpx>=0.27 这种范围):

uv pip compile requirements.in > requirements.txt

习惯上,requirements.in 是「输入」(带版本范围),requirements.txt 是「输出」(钉死的精确版本)。这是 pip-tools 的约定,uv pip compile 完全兼容。

场景二:把 requirements.txt 转成 pyproject.toml

这是更彻底的迁移。假设各位现有的 requirements.txt:

httpx>=0.27
click>=8.0
sqlalchemy>=2.0

第一步:在项目根目录跑:

uv init --no-package

--no-package 告诉 uv 这只是个应用,不打算发布到 PyPI。

第二步:把 requirements.txt 里的依赖一行一行 uv add:

uv add httpx click sqlalchemy

或者批量:

uv add -r requirements.txt

第三步:删掉老的 requirements.txt,需要兼容的话从 pyproject.toml 导出:

uv export --no-dev > requirements.txt

几个常见坑和小技巧

坑一:把 .venv 提交到 git

新手很容易忘记加 .venv/ 到 .gitignore。这事 uv init 已经帮你处理了,但如果是手动迁移的老项目,记得检查一下 .gitignore。

坑二:忘了 uv.lock

uv.lock 必须进 git。

坑三:在虚拟环境里全局装包

各位有时候会习惯性 pip install something,结果装到了系统 Python 里。在 uv 项目里:

  • 加项目依赖用 uv add
  • 想全局装一个 CLI 工具用 uv tool install
  • 想临时跑一次用 uvx

不要在 uv 项目里直接 pip install。

技巧一:缓存全局共享

uv 有个全局缓存,所有项目共享下载过的包:

uv cache dir

新项目第一次 uv sync,如果包在缓存里,秒装完,不用重新下载。这是 uv「快」的另一个原因。

清理缓存:

uv cache clean

平时不需要清,磁盘紧张了再清。

技巧二:把 pyproject.toml 当配置中心

pyproject.toml 不光放项目元数据,还能放工具配置。完整一点的例子:

[project]
name = "my-project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = [
    "httpx>=0.28",
]

[dependency-groups]
dev = [
    "pytest>=8.3",
    "ruff>=0.8",
    "mypy>=1.13",
]

[tool.ruff]
line-length = 100
target-version = "py312"

[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B"]

[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-v"

[tool.mypy]
python_version = "3.12"
strict = true

ruff、pytest、mypy 三个工具的配置全在一起,新人接手项目打开一个文件什么都看见了。

单文件脚本也能用 uv

各位有时候写个一次性脚本,可能就 50 行 Python,但要用 httpx。难道为这 50 行新建一个项目?

uv 支持「内联依赖」语法(PEP 723)。在脚本头部写一段元数据:

python
# /// script
# requires-python = ">=3.12"
# dependencies = [
#     "httpx",
# ]
# ///

import httpx

response = httpx.get("https://httpbin.org/get")
print(response.status_code)

跑:

uv run script.py

uv 会读取脚本头部的元数据,自动建一个临时环境装上 httpx,跑完即丢。单个 .py 文件就是个完整项目,发给同事一份就能跑。

小结

这一章信息量大,最后给各位提炼成几条:

第一,pyproject.toml 取代了一切。 setup.py、setup.cfg、requirements.txt、requirements-dev.txt、pytest.ini、.flake8、tox.ini 一堆零碎文件,2026 年都可以收进 pyproject.toml 一个文件。

第二,uv 是 2026 年的首选包管理器。 Astral 出品,Rust 写的,10-100x 速度,单二进制,统一管理 Python 版本、虚拟环境、依赖。

第三,记住这五条核心命令。

uv init my-project       # 创建项目
uv add httpx             # 加生产依赖
uv add --dev pytest      # 加开发依赖
uv sync                  # 同步环境(同事拉下来用)
uv run python main.py    # 跑命令

第四,uv.lock 必须进 git。 这是「环境字节级一致」的保证。

第五,老项目用 uv pip compile 生成 requirements.txt,新项目用 uv add + uv sync。

「我电脑能跑你电脑跑不了」这句话,2026 年起,可以扔进历史的垃圾桶了。

本章目录

pyproject.toml + uv

  1. 教学 01pyproject.toml + uv:彻底告别「我电脑能跑你电脑跑不了」
  2. 教学 02pyproject.toml 是个啥
  3. 教学 03uv 是什么 & 怎么装
  4. 教学 04uv add / uv run:日常工作流
  5. 教学 05uv sync:环境字节级一致
  6. 教学 06uv vs pip vs poetry vs pipenv
← 上一章22 · async / await
pyproject.toml + uv
下一章 →24 · ruff:一站式代码检查