首页
/ Black 版本演进全解:从 CHANGES.md 读懂 Black 的 CalVer 版本机制、年度稳定风格与发布流程

Black 版本演进全解:从 CHANGES.md 读懂 Black 的 CalVer 版本机制、年度稳定风格与发布流程

2026-09-05 18:04:46作者:傅爽业Veleda

本篇技术指南以 Black 仓库根目录下的变更日志 CHANGES.md 为核心素材,系统拆解 Black 自 2018 年首个发布版至今的版本命名规则、变更日志的分类体系、每年一次的“稳定风格”升级机制,以及日志条目背后的发布流程(CalVer 版本号、Unreleased 模板、scripts/release.py 自动化)。读完之后,你将能够独立解读任意一条 Black changelog 条目的含义,准确判断某次升级是否可能改变你项目的格式化结果,并知道如何配合 --preview/--unstable 实验新风格。

一、CHANGES.md 是什么:一份 2400+ 行、覆盖全部发布历史的单一变更日志

Black 把完整的版本历史集中在仓库根目录的 CHANGES.md 中(从 2018 年的 18.3a0 到当前仓库中的 Unreleased 区块,共 2493 行)。文档站则通过 docs/change_log.md 用 Sphinx 的 include 指令直接嵌入该文件,保证线上文档与仓库日志永不脱节。

日志的组织结构高度固定,自顶向下分为两部分:

  1. ## Unreleased 区块:所有已合并但尚未随版本发布的变更,持续累积;
  2. ## Version X.Y.Z 区块:按时间倒序排列的每个已发布版本的条目。

每个版本区块内部再按固定的分类小节组织。以当前 Unreleased 区块为例,它包含如下小节(这些分类名在整份日志中保持一致,是理解 Black 变更的第一把钥匙):

分类小节 含义 典型条目举例(来自 CHANGES.md
Highlights 该版本尤其重大或具破坏性的变更 26.5.0:支持 PEP 798(推导式中解包)与 PEP 810(惰性导入),即 Python 3.15 的两个新语法特性
Stable style 影响 Black 稳定风格的修复(通常只修 bug,不改变既有格式化行为) Unreleased:修复 --skip-magic-trailing-comma 下单元素下标 a[x,] 尾逗号被丢弃的问题 (#5272)
Preview style 影响 --preview 预览风格的变更,无稳定性承诺 Unreleased:移除生成器表达式周围的冗余括号 (#5304)
Configuration 配置行为变更:pyproject.toml、缓存、--line-ranges、环境变量等 Unreleased:校验 --line-ranges 取值 (#5107)、忽略空缓存文件 (#5192)、BLACK_NUM_WORKERS 非法值报用法错误而非崩溃 (#5211)
Packaging 打包与依赖、wheel/二进制构建 25.12.0:发布物新增 arm64 Windows 二进制与 wheel (#4814)
Parser 解析器与 Python 版本自动检测 24.4.1:支持 Python 3.12 的 PEP 701 f-string 新语法 (#3822)
Performance 性能改进 25.9.0:重写 tokenizer 以提升性能与规范符合度 (#4536);Unreleased 区块一次列出了 20 余条字符串合并、括号嵌套扫描、# fmt: skip 处理路径的性能优化
Output 终端输出与错误信息 26.5.0:解析错误改为带错误指针的多行输出 (#5068),新增 SourceASTParseError 区分源码解析失败与内部安全检查失败 (#5080)
_Blackd_ HTTP 服务端 blackd 的变更 26.5.0:源码解析失败返回 HTTP 400 而非 500,500 仅保留给真正的内部安全错误 (#5080)
Integrations Docker、GitHub Actions、pre-commit、编辑器等集成 23.9.0:官方 pre-commit 镜像发布,替换 .pre-commit-config.yaml 中的 URL 可提速约 2 倍 (#3828)
Documentation 文档与政策层面的重要变更 26.1.0:changelog 改用 “Version X.Y.Z” 标题以便生成稳定的 ReadTheDocs 锚点 (#5063)

Unreleased 区块顶部还保留给 PR 作者的提示注释:“请在 changelog 条目中包含 PR 编号而非 issue 编号”——这正是日志中每条变更末尾 (#5246) 这类括注的含义:指向合并该变更的 PR。

二、版本命名:CalVer(YY.M.N)如何从日志中显现

CHANGES.md 的标题序列可以直接还原出 Black 的版本命名规律:

  • 最早是 2018 年 3 月的 18.3a0(“first published version, Happy 🍰 Day 2018!”),此后经过 18.3a1~18.6b421.12b0 等 alpha/beta 系列,在 22.1.0 完成转正;
  • 转正后采用严格的 CalVer YY.M.N 格式22.3.022.6.023.1.025.12.026.1.026.3.026.5.026.5.1,即“年份后两位.月份.当月第几个修订号”。

这一约定并非只是日志习惯,而是发布流程的正式规定。docs/contributing/release_process.md 明确写道:“Black 遵循 [CalVer] 版本标准,使用 YY.M.N 格式……例如 2026 年 1 月的第一个发布即为 26.1.0”,并规定发布节奏为“每 1-2 个月发布一次 main 上的内容,正常情况下每月最多一个版本”。版本号由 scripts/release.py 自动计算并打印,供维护者直接粘贴。

从日志中还能观察到两类特殊版本号的使用方式:

  1. 补丁号递增的 bugfix 版本:如 24.4.1(修复 24.4.0 新 f-string 解析器引入的两个回归)和 24.4.2(同一解析器的后续 hotfix),26.5.1(修复 26.5.0 中注解下标内联注释导致的不稳定格式 #5130 与 # type: ignore 注释丢失 #5139)。这类版本通常只包含 Stable stylePackaging 小节。
  2. 预发布后缀:早期大量 a(alpha)与 b(beta)版本,例如 21.11b0(match 语句部分支持)、21.12b0(Python 3.10 支持)。22.1.0 是“first non-beta release”,也是第一个受稳定策略约束的版本

三、年度稳定风格:日志中周期性出现的 “Introduces the XXXX stable style”

CHANGES.md 中最具特色的模式,是每年 1 月(或最接近 1 月的版本)出现一段 “This release introduces the new XXXX stable style, stabilizing the following changes” 的汇总。这份日志里完整记录了四次这样的年度定型:

  • 23.1.0(2023 稳定风格):将上一年 --preview 中的大部分行为定型,如带粘性前导注释的类/函数前强制空行 (#3302)、函数参数中的隐式拼接字符串加括号 (#3307)、--skip-magic-trailing-comma 对下标尾逗号的处理 (#3209) 等,条目中还逐条标注了这些变更最初进入 preview 的版本(如 22.12.0、22.8.0、22.3.0);
  • 24.1.0(2024 稳定风格):定型了 if-else 表达式加括号 (#2278)、赋值过长时优先拆分右侧 (#3368)、模块 docstring 后强制换行 (#3932)、长类型注解加括号 (#3899) 等 15 项变更;同时日志注明:--preview 从此只包含“预期进入明年稳定风格”的特性,有已知问题的特性被移入新增的 --unstable 风格,并提供 --enable-unstable-feature 标志按特性粒度启用(如 --enable-unstable-feature hug_parens_with_braces_and_square_brackets,#4096);
  • 25.1.0(2025 稳定风格):定型了 Unicode 转义十六进制码统一小写 (#2916)、typed 函数参数一致地加尾逗号 (#4164)、case 块 if 守卫中的冗余括号处理 (#4214/#4269) 等 9 项变更,另含两项“此前任何版本都不曾出现”的新变化(如泛型函数定义先拆参数再拆类型参数,#4553);
  • 26.1.0(2026 稳定风格):定型了 always_one_newline_after_importmultiline_string_handlingstandardize_type_commentsremove_parens_from_assignment_lhs 等 9 个特性。

为什么是每年一次?这由 Black 的稳定策略决定,原文见 docs/the_black_code_style/index.md

  • 同一日历年内,用同一套选项格式化过的代码,再用该年内任何其他版本格式化都保持不变——“这意味着项目可以安心使用 black ~= 26.0,而不用担心 2026 年内格式化变化破坏项目”;
  • 新日历年的第一个版本可能包含格式化变化,但会尽可能最小化;
  • --preview--unstable 明确豁免于该策略,不承诺输出稳定。

日志与源码可以相互印证这一机制:src/black/mode.py 中的 Preview 枚举(string_processinghug_comparatorparenthesize_tuple_in_yieldremove_redundant_generator_parentheses 等 13 个特性)正是 --preview 风格背后的特性开关,UNSTABLE_FEATURES 集合标记了已知问题特性(string_processinghug_parens_with_braces_and_square_brackets),而 Mode.__contains__ 实现了“unstable 模式全开、preview 模式除不稳定特性外全开、--enable-unstable-feature 单独点亮”的语义——与 24.1.0 日志条目描述的行为一一对应。

CHANGES.md 还完整保留了这一机制的演化过程:--preview 标志诞生于 22.1.0 (#2752),此前实验特性挂在 --experimental-string-processing 上(22.1.0 中该标志被弃用并并入 --preview,24.1.0 中被彻底移除);23.12.0 的 Highlights 则预告了“24.1a1 alpha 版将展示 2024 稳定风格草案,请试用并反馈”。

四、从日志中复盘 Black 的能力演进主线

把 2493 行日志按主题切片,可以还原出 Black 功能发展的几条清晰主线,对判断“某个版本能做什么”很有帮助。

4.1 命令行选项的引入时间线

几乎每个重要选项都在日志中留下了出生证明,且条目里带短横线/长横线两种形式:

选项 引入版本(日志条目)
--check 18.3a1
--diff 18.4a0
--quiet 18.4a1(同版还有自动括号管理、pre-commit 集成)
--pyi--py36 18.5b0(.pyi 按 PEP 484 风格格式化)
--include/--exclude--skip-string-normalization--verbose 18.6b0
--config 18.6b2(#65)
--force-exclude--extend-exclude--stdin-filename# fmt: skip--skip-magic-trailing-comma/-C 18.8b0 至 21.4b0 区间(#1032、#2005、#1780、#1800、#1824)
--workers 21.10b0(#2514),23.7.0 起可经环境变量 BLACK_NUM_WORKERS 设置(#3743)
--required-version 21.6b0(#2300),22.3.0 起支持稳定主版本形式(#2832)
--target-version 19.3b0(#618),取代 --py36
--line-ranges 23.11.0(#4020),23.12.0 修复其与 --safe 内部稳定性检查的冲突(#4034),Unreleased 中又修复了范围仅覆盖 docstring 时多插空行的问题 (#5312) 并加入取值校验(#5107)
--preview 22.1.0(#2752)
--skip-source-first-line/-x 22.10.0(#3299)

4.2 运行时 Python 支持的政策(以日志为准)

日志精确记录了每次“不再支持哪个运行时版本”的边界,这是选择 Black 运行环境时的硬约束:

  • 22.10.0:移除 Python 3.6 运行时支持(仍支持格式化 3.6 代码);
  • 23.7.0:移除 Python 3.7 运行时支持;
  • 25.12.0:不再支持在 Python 3.9 上运行;
  • 当前仓库 pyproject.tomlrequires-python = ">=3.10",与日志口径一致。

4.3 解析器对新语言特性的跟进

Parser 小节是语言特性支持的“时间戳档案”,且条目普遍注明对应 PEP 与 PR:PEP 646/654(22.6.0,#3016/#3071)、PEP 701 f-string(24.4.1,#3822)、PEP 695 类型参数语法(23.7.0 起分阶段支持,#3703 等)、PEP 750 t-string(25.11.0,#4805)、PEP 798/810(26.5.0,#5048)。日志还记录了两次解析器层面的大工程:21.1.0 中 mypyc 编译带来约 2 倍整体提速(#1009/#2431)、25.9.0 的 tokenizer 全面重写(#4536)。

4.4 安全与破坏性变更如何被标注

日志对“必须立即升级”的事项会用醒目的 Highlights 说明,最典型的是 24.3.0:修复 Black 的第一个 CVE(CVE-2024-21503,docstring 中含大量前导 tab 时的灾难性性能退化,#4278),正文明确写道“如果你在不受信任的输入上运行 Black……强烈建议立即升级”;同版本还强化了 AST 安全检查,宁可崩溃也不错误改写某些嵌套同引号 f-string(#4270)。另一类破坏性变更则写在条目里并给出迁移方案,例如 26.1.0pathspec 升级到 v1 后 .gitignore 匹配逻辑向 Git 对齐:之前被“父目录取消忽略”救回的 exclude/not_this/foo.py 现在保持忽略,日志直接给出新的 .gitignore 写法(*/exclude/* + !*/exclude/not_this/)作为迁移指引。

五、Unreleased 区块:一份“下一版会怎样”的实时预告

当前仓库 CHANGES.mdUnreleased 区块(第 3-214 行)展示了待发布变更的最新状态,也是观察 Black 演进方向的最佳窗口。按小节归纳:

  • Stable style:一批边界条件修复,每条都写明了“为什么会出错”。例如 #5321(保留 (x): int = 5 中注解赋值目标的括号,因为去掉括号会使名字从 __annotations__ 中消失,从而触发 Black 自己的 AST 安全检查);#5287(不再把 t-string 当作 docstring 处理,因为 t"..." 求值结果是 Template 而非 str,strip/缩进会改变模板值);#5262/#5265(两类导致“Black 无法解析自己输出”的字符串转义 bug)。
  • Preview style:9 项新行为,如移除生成器表达式冗余括号 (#5304)、yield 中的元组表达式加括号以对齐函数调用与 return (#5170)、停止在右侧为括号表达式时在比较符处断行 (#5135)、hug 括号时避免把两条 type: ignore 合并到一行 (#5271) 等。
  • Configurationfind_project_root--code 场景下的 CWD 缓存过期修复 (#5152)、--line-ranges 取值校验 (#5107)、缓存文件的空文件/权限错误容错 (#5192/#5258)。
  • Performance:一次集中了约 20 条优化,全部给出“旧实现为何慢”的机制说明,例如 blib2to3 兄弟节点映射改为增量维护而非每次树变更全量重建 (#5178)、pre_order/post_order/leaves 由递归生成器委托改为迭代遍历以消除嵌套深度带来的平方级开销 (#5235)、# fmt: skip 指令不再整树重扫 (#5169)。
  • Output:解析失败改用编辑器友好的 path:line:column 定位 (#5237)。

这种“每条变更自带原因解释”的写作风格贯穿全文件,也是 Black changelog 值得借鉴的地方:条目不止说“做了什么”,还说“为什么必须这么做”(往往是避免 AST 安全检查失败或避免不幂等的输出)。

六、发布流程:CHANGES.md 与自动化脚本如何咬合

docs/contributing/release_process.md 描述了围绕这份日志的完整发布流水线,关键步骤与 CHANGES.md 直接相关:

  1. 准备发布 PR:运行 scripts/release.py 自动把 ## Unreleased 头替换为版本号、删除空小节,并计算 YY.M.N 版本号;发布前会用 git diff origin/stable CHANGES.md 复查条目是否放错了小节;
  2. 发布 GitHub Release:草稿 Release 打 YY.M.N 标签、粘贴 changelog 原文,发布后触发 GitHub Actions 的发布自动化——构建 sdist + 纯 Python wheel(Hatch)、用 mypyc + cibuildwheel 构建各平台编译 wheel(22.1.0 起 Black 已 mypyc 化,日志 #1009/#2431)、PyInstaller 原生二进制、Docker 镜像(amd64/arm64),并经 Trusted Publishing 上传 PyPI(26.1.0 日志条目 #4611 记录该升级);
  3. 发布后收尾update-stable 工作流把 stable 分支强推到最新 tag;new-changelog 工作流自动开一个不自动合并的 PR,用 release.py --add-changes-template 生成新的空 Unreleased 模板放回 CHANGES.md 顶部——即日志文件头那套 Highlights/Stable style/Preview style/... 带 HTML 注释占位的小节骨架。该模板当前就保存在 scripts/release.pyNEW_VERSION_CHANGELOG_TEMPLATE 常量中。

对使用者而言,这一流程意味着:stable 分支对应“最新稳定 tag”,而 main 分支上的 CHANGES.md 永远比 PyPI 上的最新 wheel 多出一个 Unreleased 区块,是判断“下个版本将带来什么”的最权威一手材料。

七、实战:如何依据 CHANGES.md 做版本选型与升级

结合日志事实与稳定策略,可以给出几条可操作的结论:

  1. 锁定年内版本:若项目已在 2026 年的某个 Black 上格式化过,可用 black ~= 26.0 允许月版本升级而不改变既有代码格式(依据 docs/the_black_code_style/index.md 的稳定策略原文);跨年到 27.x 的第一个版本时,需预期可能出现新的稳定风格变化,并对照该版本的 “Introduces the 2027 stable style” 条目列表逐项评估。
  2. 升级前做 diff 演练:对跨大版本升级(如 25.x → 26.x),先跑 black --check --diff。26.1.0 是 2026 年的风格定型版本,日志列出的 9 个定型特性(import 后空行、# type: 注释标准化、except 类型去括号等)就是 diff 的来源;26.1.0 还有一处行为变更需要特别留意:pathspec v1 使 .gitignore 的“父目录取消忽略”不再放行子文件,若仓库依赖旧行为需按日志给出的新写法调整 .gitignore
  3. 实验新风格要显式声明--preview/--unstable 豁免稳定策略(日志 22.1.0、24.1.0 条目均有明文),只应在独立分支上试跑;对单个不稳定的特性,用 --enable-unstable-feature <特性名> 精确启用,特性名可对照 src/black/mode.pyPreview 枚举。
  4. 把崩溃当作特性缺口信号:日志显示 Black 在“AST 安全检查失败”时会选择报错/崩溃而非产出不可证明等价的代码(如 24.3.0 的 #4270、Unreleased 的 #5321)。遇到 INTERNAL ERROR 时,应检查目标版本与运行版本的匹配(26.3.0 起,目标版本高于运行时版本会给出明确警告而非误导性报错,#4983),并将输入代码片段提交为复现用例——这是该日志中绝大多数 Stable style 条目的来源形态。
  5. blackd 部署看 _Blackd_ 小节:26.3.1 起 blackd 默认禁用浏览器来源请求、支持 origin 白名单与请求体限制 (#5039);26.5.0 起解析失败返回 HTTP 400、内部安全错误才返回 500 (#5080)。升级 blackd 客户端/网关前应对照这两条调整监控口径。

结语

CHANGES.md 不只是一份流水账:它以 CalVer YY.M.N 版本号为时间轴,以固定分类小节为坐标系,完整编码了 Black 从 18.3a0 到 26.5.x 的风格演化、运行时支持边界、解析器能力与安全承诺;再配合 docs/contributing/release_process.md 描述的 CalVer 版本号计算、Unreleased 模板回填与 stable 分支强推机制,构成了 Black “年内稳定、年初定型、特性经 preview/unstable 两级漏斗晋升”的可验证发布体系。掌握这套读法,你就能在任意一次 pip install -U black 之前,精确回答“它会改变我代码的哪些行”。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.83 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.79 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
988
506
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384