最稳妥的方式是用 setuptools-scm 实时从 git 标签和提交历史推导版本号,要求仓库为有效 git 仓库、含 pep 440 兼容的附注标签,且 pyproject.toml 正确配置 build-backend 和 tool.setuptools-scm。

直接用 setuptools-scm 是最稳妥的方式——它不碰 __version__ 字符串,也不依赖手动维护 setup.py 里的版本字段,而是实时从 Git 提交历史和标签里推导版本号。前提是你的仓库必须是有效的 Git 仓库,且有符合 PEP 440 的标签(如 v2.1.0、2.1.0)。
确保 Git 仓库结构满足 setuptools-scm 要求
它只读取工作目录下的 .git 目录,不会走远程或子模块。常见失败原因不是配置错,而是 Git 状态不对:
- 项目根目录下没有
.git(比如用cp -r复制源码但没带 .git) - 当前分支没关联任何远程,但你又启用了
write_to或local_scheme中依赖远程的逻辑(如node-and-date) - 最近的 tag 是轻量 tag(lightweight tag),不是附注 tag(annotated tag)——
setuptools-scm默认只信任附注 tag,除非显式设fallback_version或改version_scheme - Git 工作区有未提交变更,而你用了
dirty=True(默认开启),会导致版本号末尾自动加+dYYYYMMDD或.dirty,CI 环境中可能意外触发
在 pyproject.toml 中正确启用 setuptools-scm
不再推荐用 setup.py,现代方式是纯 pyproject.toml 配置。关键点在于:必须声明 build-backend 并启用插件,否则 pip install -e . 或 build 时根本不会调用它:
[build-system] requires = ["setuptools>=45", "setuptools-scm[toml]>=6.2", "wheel"] build-backend = "setuptools.build_meta" <p>[project] name = "mylib"</p><h1>不要写 version = "1.0.0" —— 这会覆盖 setuptools-scm 的自动推导!</h1><p>[tool.setuptools-scm]</p><h1>常用配置项</h1><p>version_scheme = "guess-next-dev" local_scheme = "dirty-tag" write_to = "mylib/_version.py"</p>
注意:write_to 是可选的,仅当你需要在运行时读取版本(如 mylib.__version__)才设;如果只用于构建分发包(sdist/wheel),完全可以不写。
处理 dev 版本与预发布标签的常见行为
如果你打过 v2.1.0a1 或 v2.1.0rc2 这类预发布 tag,setuptools-scm 默认会生成类似 2.1.0a1.dev3+gabc123 的版本号。但容易踩坑的是:
-
version_scheme = "guess-next-dev"(默认)会在最新 tag 后自动升 patch 并加.devN,比如最新 tag 是v2.1.0,当前 HEAD 比它多 3 次提交 → 版本为2.1.1.dev3;若想保持2.1.0.dev3,得用no-guess-dev - 如果你用
git describe --tags手动验证,结果可能和setuptools-scm不一致——因为它默认忽略轻量 tag,而git describe默认包含;可用git describe --tags --abbrev=0对齐逻辑 - CI 构建时若用 shallow clone(
git clone --depth=1),setuptools-scm会 fallback 到fallback_version(需手动配置),否则报错Command 'git' failed with exit code 128
验证版本是否真正生效
别只信 python -m build 输出的日志。最可靠的验证方式是实际安装后检查:
pip install -e . python -c "import mylib; print(mylib.__version__)"
如果报 AttributeError: module 'mylib' has no attribute '__version__',说明要么没设 write_to,要么 write_to 路径没被 __init__.py 导入;如果输出是 0.0.0,大概率是 Git 根目录识别失败,或者最近没有合法 tag——此时运行 python -m setuptools_scm(在项目根目录)能直接打印诊断信息,包括它找到的 tag、距离、是否 dirty 等。
真正麻烦的永远不是配置本身,而是 Git 历史的“干净程度”:tag 是否附注、HEAD 是否在 tag 上、是否有 untracked 文件影响 dirty 判断。这些细节不暴露在配置里,却决定最终版本号是否符合预期。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











