Black 贡献者上手指南:开发环境搭建、测试体系与风格变更规范
本文基于 Black 仓库官方贡献文档 docs/contributing/the_basics.md 展开,系统讲解如何从零搭建 Black 的开发环境、提交代码前必须执行的 lint 与测试命令、基于 tests/data/cases 的三段式格式化测试文件如何编写,以及 --print-full-tree/--print-tree-diff 调试开关、CHANGES.md 变更日志要求和风格变更的稳定性政策。读完并动手实践后,你将能够独立为 Black 提交通过 CI 检查的补丁。
一、环境准备:从克隆仓库到可运行的开发环境
官方建议优先使用最新版本的 Python 进行开发,操作系统不限。当前仓库 pyproject.toml 声明 requires-python = ">=3.10",且项目元数据覆盖 CPython 3.10 到 3.15,所以用 3.10+ 的任何发行版都可以起步。
第一步是克隆 Black 仓库并进入目录:
$ git clone https://gitcode.com/GitHub_Trending/bl/black
$ cd black
然后创建一个虚拟环境(任选你熟悉的方式),安装开发依赖,并挂上 git pre-commit 钩子:
$ python3 -m venv .venv
$ source .venv/bin/activate # linux / macOS 激活方式
$ .venv\Scripts\activate # Windows 激活方式
(.venv)$ pip install --group dev
(.venv)$ pip install -e ".[d]"
(.venv)$ pre-commit install
这几条命令各自的作用,可以从仓库的 pyproject.toml 中得到印证:
pip install --group dev安装的是 PEP 735 依赖组([dependency-groups]中的dev组)。从源码结构看,dev组实际是{ include-group = "cov-tests" }, { include-group = "tox" }, "pre-commit"的组合,其中cov-tests又聚合了coverage、tests两组并额外加上pytest-cov>=4.1.0;tests组提供pytest>=7与pytest-xdist>=3.0.2。也就是说这一条命令一次性装齐了 pytest、覆盖率与并行测试所需的工具链。pip install -e ".[d]"以可编辑模式安装 Black 本体及其d可选依赖(在pyproject.toml中定义为aiohttp>=3.10,用于blackdHTTP 服务,对应 src/blackd/ 模块)。pip install --group docs则用于后面文档构建测试,安装sphinx==9.1.0、furo、myst-parser等文档工具链。pre-commit install注册钩子后,每次git commit都会自动执行仓库内置的.pre-commit-config.yaml。该配置包含 isort、flake8(含 flake8-bugbear 等插件)、mypy、prettier(格式化 Markdown/YAML/JSON)以及 pre-commit-hooks 的end-of-file-fixer、trailing-whitespace等钩子。值得注意的是其中两个 local 钩子:check-pre-commit-rev-in-example与check-version-in-the-basics-example(入口分别是scripts/check_pre_commit_rev_in_example.py和scripts/check_version_in_basics_example.py),它们专门校验文档示例中的版本号/rev 是否与仓库一致——也就是说本文对应的the_basics.md文件本身就处于这些自动化检查的覆盖之下。
二、提交 PR 前必须执行的四条命令
在从 Black 仓库根目录提交 pull request 之前,需要运行以下命令完成 lint 与测试:
(.venv)$ pre-commit run -a # Linting
(.venv)$ tox -e py # Unit tests
(.venv)$ tox -e fuzz # Optional Fuzz testing
(.venv)$ tox -e run_self # Format Black itself
结合 tox.ini 可以看到每条命令背后实际发生的事:
pre-commit run -a:对全部文件执行上节提到的所有钩子,等价于把「commit 时才会跑的检查」手动完整跑一遍。tox -e py:envlist为{,ci-}py{310,311,312,313,314,315,py3},fuzz,run_self,generate_schema,py是其中不带版本号的默认环境。该环境的命令序列是:先pip install -e .[d]、coverage erase,再运行pytest tests --run-optional no_jupyter --numprocesses auto --cov(不装 jupyter 依赖的测试);随后pip install -e .[jupyter]再跑pytest tests --run-optional jupyter -m jupyter --cov --cov-append(jupyter 相关测试),最后coverage report。[testenv]中recreate = True的注释解释了原因:避免第二次运行时no_jupyter测试在已安装 jupyter 依赖的环境下被错误执行。tox -e fuzz:使用hypothesis+hypothesmith做模糊测试,实际执行coverage run scripts/fuzz.py(入口脚本见 scripts/fuzz.py)。这一步是可选的。tox -e run_self:Black 用最新源码格式化它自己(black --check {toxinidir}),保证「格式化器的输出对自己稳定」这一底线。
更多测试调用示例
日常开发中常用的补充调用方式:
(.venv)$ tox --parallel=auto # Run all the above in parallel
(.venv)$ tox -e py314 # Run tests on a specific python version
(.venv)$ pytest -k <test name> # Run an individual test
(.venv)$ tox -e py -- --no-cov # Pass arguments to pytest
tox -e py314对应envlist中的py314环境,可在指定 Python 版本上单独验证行为(对涉及--minimum-version的版本相关用例尤其有用);tox -e py -- --no-cov这类--之后的参数会透传给testenv命令里的{posargs},最终传给 pytest;- 直接
pytest -k <test name>按名称过滤单个测试,是最快的迭代方式。
三、测试体系:tests/data/cases 中的三段式测试文件
Black 要求「风格的所有方面都应有测试覆盖」。测试通常创建为 tests/data/cases 目录下的文件,每个文件最多由三部分组成:
# flags:开头的行,后跟一组命令行选项。例如# flags: --preview --skip-magic-trailing-comma表示该用例在开启 preview 模式、关闭 magic trailing comma 的条件下运行。可接受的选项大体是 Black 自身命令行选项的一个子集,其中--minimum-version=有特殊用途:当测试「只有新版本 Python 才能解析的语法特性」时使用,它确保不会在旧版本上尝试做 AST 校验,同时验证 Black 在特性被使用时能正确自动探测 Python 版本。省略该行则使用默认选项。- 一段作为格式化输入的 Python 代码块。
# output行,其后的内容是 Black 对上面代码块运行后应产生的输出。若省略该行,则测试断言 Black 应保持输入代码原样不变。
仓库中可以找到真实示例:tests/data/cases/backslash_before_indent.py 首行为 # flags: --minimum-version=3.10,tests/data/cases/comment_type_hint.py 首行为 # flags: --no-preview-line-length-1,都是典型的 flags 行用例。
flags 行的解析实现
「可接受的选项集合」在 tests/util.py 的 get_flags_parser() 函数中定义(约第 235 行起)。从源码结构看,该解析器用 argparse 注册了 --target-version、--line-length、--skip-string-normalization、--pyi、--ipynb、--skip-magic-trailing-comma、--preview、--unstable、--fast、--minimum-version、--line-ranges、--no-preview-line-length-1 等参数。其中 --minimum-version 的帮助文本解释了运行机制:设置后该测试用例会被运行两次——一次不带 --target-version,另一次将 --target-version 精确设为指定版本,以此保证版本自动探测逻辑正确。这解释了官方文档中「同时测试自动探测」一句的实现细节。
四、失败用例调试:--print-full-tree 与 --print-tree-diff
Black 有两个作用于 tests/data/ 中「输入/输出分离式」测试文件的 pytest 命令行选项,既可以通过 tox 透传,也可以直接传给 pytest。
--print-full-tree
测试失败时,打印处理完输入之后的完整具体语法树(CST,"actual"),以及解析输出代码后得到的树("expected")。注意:测试可能因输出不同而失败,但两棵 CST 完全相同。文档特别指出,这个行为曾经是默认值,但现在默认是 False。
(.venv)$ tox -e py -- --print-full-tree
--print-tree-diff
测试失败时,打印上述两棵树的 diff。这是默认开启的行为;想关闭可传 --print-tree-diff=False。
(.venv)$ tox -e py -- --print-tree-diff=False
这两个选项的实现在 tests/conftest.py 中:pytest_addoption() 注册两个选项,模块级默认值 PRINT_FULL_TREE = False、PRINT_TREE_DIFF = True 与文档描述完全一致,pytest_configure() 再把选项值写回这两个全局变量供断言逻辑使用。配套的单元测试在 tests/test_black.py 中,test_assertFormatEqual_print_full_tree 验证失败输出里出现 Expected tree: 与 Actual tree:,test_assertFormatEqual_print_tree_diff 验证 diff 输出里出现 Tree Diff: 与新增的 COMMA 节点——两者均标记 incompatible_with_mypyc(mypyc 编译构建下跳过)。
排查思路建议:先看默认的 tree diff 定位是「哪类节点/叶子」处理分歧;若 diff 看不出问题(例如两棵树相同但序列化输出不同),再开 --print-full-tree 对照完整结构。
五、CHANGES.md 变更日志要求
Black 的 CI 会检查每个 PR 是否在 CHANGES.md 中有对应条目。如果认为你的 PR 不需要变更日志,可在 PR 评论中说明,由维护者添加 ci: skip news 标签让 CI 通过。否则,请确保在合适的标题下方添加一行如下格式的条目:
- `Black` is now more awesome (#X)
注意 X 应是你的 PR 编号,而不是 issue 编号。原文档建议用一个名为 "Next PR Number" 的外部统计工具来估算下一个 PR 号(此处不附外链);这种方式虽不完美,但能显著降低发版工作量——发版者无需再逐条回溯每个发布应该往 CHANGES.md 里补什么内容。
六、风格变更规范与稳定性政策
在提交任何改动前,先熟悉 Black 的稳定性政策(Stability Policy)。据此,大多数风格变更必须加到 --preview 风格中,例外只有两类:修复崩溃,以及不会影响「已经被格式化过文件」的改动。
具体的文档与日志配套规则:
- 若改动影响对外宣传的代码风格,请同步修改 Black 代码风格文档(即 docs/the_black_code_style/current_style.md)以反映该变化;修复格式化中非预期 bug 的补丁则不需要单独提及。
- 若改动是通过
--preview标志实现的,请把该变化写入 docs/the_black_code_style/future_style.md(Future Style 文档),并把变更日志条目写在专门的 "Preview style" 标题下。
从稳定性政策原文可以看到其含义:同一日历年内,用某版本 Black 格式化过的代码,再用同一年其他版本(相同选项)格式化时应保持不变(因此项目可以安全使用 black ~= 26.0 这类兼容约束);新年首个版本可能包含尽量少的格式化变更,以纳入新语言语法的改进;而 --preview 与 --unstable 标志则豁免于该政策。这也解释了为什么 tests/data/cases 中大量 preview_ 前缀用例(如 preview_hug_comparator.py)带有 --preview flags 行。
七、文档构建测试
如果你修改了文档,可以在本地验证其仍可构建:
(.venv)$ pip install --group docs
(.venv)$ pip install -e ".[d]"
(.venv)$ sphinx-build -a -b html -W docs/ docs/_build/
其中 -a 强制全部重建,-b html 输出 HTML,-W 把警告提升为错误——这意味着任何失效的交叉引用、拼错的锚点都会让构建直接失败,与 CI 的严格程度对齐。文档源码位于 docs/(含 docs/conf.py 与 docs/Makefile),输出目录 docs/_build/ 为本地产物。
顺带说明本文开头提到的「相对链接转换」问题在 Sphinx 项目中的对应物:Black 文档使用 myst-parser(pyproject.toml docs 组中的 myst-parser==5.1.0),正文中的 (labels/stability-policy)= 这类显式锚点定义(见 docs/the_black_code_style/index.md)正是各文档互相引用稳定性政策小节所依赖的机制。
八、工程卫生习惯
文档最后强调了两条卫生习惯,值得固化为肌肉记忆:
- 修 bug 时加测试:先写测试并运行确认它失败,然后修复 bug,再运行测试确认它真的被修复(红-绿循环);
- 加新特性时加测试:事实上任何时候都应加测试;如果要加大型特性,请先开 issue 讨论后再动手实现。
以上流程——环境搭建(依赖组 + pre-commit 钩子)、四条提交前命令(pre-commit/tox 单元测试/模糊测试/自我格式化)、三段式 cases 测试文件与 get_flags_parser 支持的 flags、tree diff 调试开关、CHANGES.md 编号规范、--preview 风格变更与稳定性政策、sphinx -W 文档构建——共同构成了 Black 仓库当前贡献流程的完整闭环,全部命令均可在 docs/contributing/the_basics.md 及本文引用的源码文件中直接对照验证。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00