Python项目打包时,你是不是也纠结过:该用传统的setup.py ,还是新潮的pyproject.toml ?前者是“老江湖”,配置灵活但代码冗余;后者是“新规范”,简洁清晰却兼容性存疑。本文通过一个真实项目(打包一个命令行工具),从配置复杂度、功能覆盖、工具兼容性三个维度,手把手教你“什么时候用setup.py ,什么时候必须上pyproject.toml ”,让你不再被“打包配置”劝退。
setup.py 本质是可执行Python脚本,通过调用setuptools的setup()函数定义打包信息:
python复制from setuptools import setup, find_packages
setup(
name="mycli",
version="1.0.0",
packages=find_packages(exclude=["tests*"]),
entry_points={
"console_scripts": ["mycli=mycli.main:run"]
},
install_requires=["requests>=2.25.0"]
) 优点:能写条件判断(比如根据Python版本选依赖)、读取文件内容(比如从README.md 读取long_description),甚至调用外部命令生成版本号——适合“需要动态配置”的复杂项目。 缺点:代码冗余(每次都要import setup),容易写错(比如漏写packages参数导致模块没打包),且无法被现代打包工具(如pip 21.3+)完全识别。
pyproject.toml 是声明式配置文件,遵循PEP 621标准,用键值对定义项目信息,无需写Python代码:
toml复制[project]
name = "mycli"
version = "1.0.0"
dependencies = ["requests>=2.25.0"]
readme = "README.md"
[project.scripts]
mycli = "mycli.main:run"
[tool.setuptools.packages.find]
exclude = ["tests*"] 优点:结构清晰(配置项分类明确)、工具中立(支持setuptools、poetry等多种后端)、不易出错(格式有严格校验)——2023年后新建项目的“官方推荐写法”。 缺点:不支持动态逻辑(比如无法在配置里写if-else),部分老工具(如setuptools<42.0)不兼容。
以“定义项目名称、版本、依赖”为例,两者的对比像“手机拍照”vs“单反手动调参数”:
如果你的项目需要根据环境切换配置(比如Windows和Linux依赖不同包),setup.py 的优势就体现出来了:
python复制# setup.py 动态依赖示例
import sys
setup(
# ...其他配置
install_requires=[
"requests>=2.25.0",
"pywin32>=300; sys_platform == 'win32'" # 仅Windows需要
]
) 而pyproject.toml 目前不支持条件依赖(需配合setup.cfg 或工具扩展),这种场景下只能“pyproject.toml+setup.py 混合使用”——前者定义基础信息,后者处理动态逻辑。
工具 | setup.py 支持度 | pyproject.toml 支持度(PEP 621) |
|---|---|---|
pip 20.3+ | 完全支持 | 完全支持(需setuptools>=42.0) |
poetry 1.2+ | 支持(作为兼容模式) | 原生支持(推荐写法) |
pipx(安装工具) | 支持 | 支持(需项目有pyproject.toml ) |
setuptools 58+ | 支持 | 支持(需配合[project]表) |
结论:2023年后的环境(pip>=21.3+setuptools>=61.0)用pyproject.toml 完全没问题;如果需要兼容Python 3.6以下或老工具,建议保留setup.py 。 |
如果你在2024年新建项目,直接用pyproject.toml+setuptools :
python -m build(需安装build工具:pip install build),自动生成wheel和sdist包,比python setup.py sdist bdist_wheel更规范。已有setup.py 的项目不用“一刀切”删除,可分两步迁移:
toml复制[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta" 这样工具会优先读取pyproject.toml ,setup.py 仅作为“动态配置补充”。
如果需要动态逻辑(如从文件读取版本号),可在setup.py 中导入pyproject.toml 的配置,再叠加动态内容:
python复制# setup.py 示例(混合模式)
from setuptools import setup
import tomllib
with open("pyproject.toml", "rb") as f:
pyproject = tomllib.load(f)
setup(
**pyproject["project"], # 导入pyproject.toml 中的静态配置
version=generate_version(),**动态生成版本号
) 如果打包时提示“ERROR: Could not find a valid build backend”,大概率是没加[build-system]段——这是告诉工具“用setuptools打包”,必须写:
toml复制[build-system]
requires = ["setuptools>=61.","wheel"] # 至少包含这两个依赖
build-backend = "setuptools.build_meta" 传统setup.py 如果没写packages=find_packages(),会导致子模块没被打包——pyproject.toml 通过[tool.setuptools.packages.find]配置,更不容易出错3. 依赖版本写太死导致安装失败**
无论是setup.py 的install_requires还是pyproject.toml** 的dependencies,依赖版本尽量用>=而非固定版本(如requests==**2.25.0),避免用户环境冲突——这是打包配置“通用铁律”。**
记住**:打包的核心目标是“让用户能用pip install顺利安装”,配置文件只是手段。与其纠结“用哪个文件”**,不如先写一个最小配置跑通流程——毕竟,能发布到PyPI的项目,比“配置文件格式正确”重要100倍。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。