python setup.cfg
# 从日常开发说起:setup.cfg到底帮了我们什么
这些年见过不少Python项目,有的简洁利落,有的混乱得让人头疼。一个项目能不能长久维护,很多时候从它的配置文件就能看出来。今天聊聊setup.cfg,这东西看起来不起眼,但用好了能让项目清爽不少。
它是什么
setup.cfg本质上是一个INI格式的配置文件,放在项目根目录。很多人第一次见到它是在读别人项目代码的时候,看到文件夹里躺着setup.py、setup.cfg、pyproject.toml,心里犯嘀咕:这些文件到底什么关系?
打个比方,setup.py就像一个保姆,什么活都干——装包、配参数、跑测试。而setup.cfg是把这个保姆的工作清单单独列出来,让setup.py只负责执行。这样分工明确,谁该干什么一目了然。
INI格式其实特别简单,就是各种[section]下面写key = value。比如:
[metadata] name = myproject version = 1.0.0这跟你平时用的配置文件没啥两样,所以上手成本几乎为零。
它能做什么
说几个实际场景吧。有个同事负责的项目,setup.py里写了将近两百行代码,各种判断逻辑、动态获取版本号、根据环境变量决定依赖包版本。每次改版本号都得小心翼翼,生怕触动了哪根神经。
用setup.cfg之后,这些配置项被剥离出来,变成干净的数据。比如版本号直接在metadata里写着:
[metadata] version = 1.2.3依赖包列表放到options里面:
[options] install_requires = requests>=2.20 flask>=1.1这还不是最妙的。setup.cfg能做的更多是那些枯燥但重要的事情。比如你有很多测试工具需要配置(pytest、flake8、coverage),传统做法是每个工具写一个配置文件,项目根目录塞满了.coveragerc、.pylintrc、setup.cfg、tox.ini。但如果你愿意,setup.cfg可以统一管理这些工具的配置,只要在tool:前缀后面写清楚就行:
[tool:pytest] testpaths = tests [flake8] max-line-length = 120怎么使用
实际用起来分几步走。第一步,创建setup.cfg,按规范把元数据和参数写好。有个小细节容易被忽略:setup.cfg的编码一定要是UTF-8,尤其是当你的项目描述里包含中文或者特殊字符时。
第二步,把setup.py瘦身。理想情况下,setup.py只要这么几行:
fromsetuptoolsimportsetupif__name__=='__main__':setup()这听起来有点偷懒,但确实是最简洁的做法。如果setup.py里不得不保留一些逻辑(比如从环境变量读取某些值),至少要保证这些逻辑不会跑偏。
第三步,善用pbr或者setuptools-scm这类工具。比如我要让版本号跟着git tag走,只需要在setup.cfg里配置:
[options] setup_requires = setuptools-scm然后在setup.py里加上:
use_scm_version=True这样每次git打tag后,包版本自动更新,再也不用手动改数字。
最佳实践
用了这么多年,踩过不少坑,总结几条经验。第一,不要把所有东西都塞进setup.cfg。有些工具虽然支持在setup.cfg里配置,但配置项太多会影响可读性。比如pytest的配置,几行能写清楚的可以放,几十行的还是单独建conftest.py更合理。
第二,版本管理要严格。特别是依赖包的版本范围,不要图省事只写包名不写版本。比如:
install_requires = numpy>=1.18,<2.0 pandas>=1.0,<1.3这样既保证了可用性,又避免了意外升级带来的破坏。
第三,注意setup.cfg和pyproject.toml的关系。现在pyproject.toml越来越流行,很多新项目直接用它代替setup.cfg。但如果项目已经用了setup.cfg,完全没必要着急迁移。我的做法是:新项目直接用pyproject.toml,老项目保持setup.cfg,等到下次大版本升级再说。
第四,关于项目描述。很多人喜欢在description里写“这是一个XX项目”之类的话,但更好的做法是把详细说明放到long_description里,并且从README.md读取:
[metadata] description = 项目简介 long_description = file: README.md long_description_content_type = text/markdown这样描述和文档能保持一致,避免了重复修改。
和同类技术对比
setup.py是最早的方案,功能最全但也最灵活。灵活性是把双刃剑,比如有人会在setup.py里写各种动态逻辑,导致构建环境稍微不同就出问题。setup.cfg的出现就是为了解决这个痛点,它强制你只用配置,不要写复杂逻辑。
pyproject.toml是后来的继承者,用TOML格式代替了INI,而且定义了统一的构建系统接口。它能做的事情比setup.cfg更多,比如还能配置构建前端(build backend)。但从易用性角度看,setup.cfg的INI格式因为简单,对新手更友好。
还有个叫setup_requires的东西,它允许你在构建时临时安装依赖,但最好别常用。因为一旦某个依赖版本不对,整个构建就会卡住。
说到底,工具只是工具,关键看项目需求。如果项目简单、团队小,setup.py就够了。如果想让构建过程更清晰可预测,setup.cfg是个好选择。而pyproject.toml适合那些需要严格控制构建流程的项目。
每个人的喜好不同,有的团队坚决只用pyproject.toml,有的老项目用了setup.cfg好几年也懒得换。我觉得没必要强求统一,只要团队内部达成共识就行。毕竟相比于文件格式,代码质量和可维护性才是最重要的。
