包的管理体系在过去这一段时间里发展得非常迅速, 几乎到了爆炸的程度, 大家可以把过去十年当成一个观察期。在最初的阶段, 开发者主要依靠的是 pip 这个工具配合 .txt 这样的文件来进行操作, 而现在出现了 PDM、uv 等现代化的工具, 虽然种类很多, 但往往会让开发人员在选择的时候感到非常困难, 不知如何是好。这篇文章的内容会比较深入, 会去分析现在流行的各种包管理工具在设计上的想法、核心的特点是什么以及适合在什么样的情况下去使用, 目的就是帮助开发者在不同的项目环境中做出正确的技术选择和分析决策。1. 包管理工具的发展历程包管理这个东西, 它的变化情况说白了就是因为需求变得特别复杂了。一开始不过是用来安装软件的那种最简单的工具, 后来呢慢慢变成了能够提供环境隔离这种功能的, 再后来更是演变成了一种完整的解决方案。而这种解决方案里面又包含了依赖解析的功能, 同时还实现了构建和发布一体化的功能。2. 咱们可以把那些工具的全貌给弄明白, 再把它们好好分分类。咱们来根据它到底能干什么, 把这个包管理工具分成四个层次:层次代表工具这指的是主要的工作责任。适用场景基础层pip venv进行包的安装操作, 并且把运行环境隔离开。这个是一个简单的脚本, 并且是用来做学习以及实验的。锁文件层pip-tools因为要确定依赖关系, 所以需要对版本进行锁定处理。把应用的部署工作做好, 还有持续集成, 接着去做持续部署。项目管理层/ PDM /对依赖关系进行管理, 并且进行构建以及发布的操作。库开发、商业项目科学计算层Conda / Mamba在需要处理跨语言依赖关系, 同时还要对环境进行妥善管理的情况下。这涉及了数据科学这个领域, 还有机器学习这种技术。2.1 基础层pip venv该方案是官方所给出的标准设定, 其特点在于简洁明了且操作直接不过却存在功能方面的局限性也就是所能实现的功能比较少。# 创建虚拟环境python -m venv venvsource venv/bin/activate # Linux/macOSvenv\Scripts\activate # Windows# 安装依赖pip install requests flask2.3.0pip freeze requirements.txt# 从文件安装pip install -r requirements.txt痛点2.第2层是锁定的文件层面, 这里所使用的工具是pip-tools。通过把这个东西弄成编译机制的办法, 把依赖声明和依赖锁定这两个事情给分开放置开来的这样一个方式。# 安装pip install pip-tools# 1. 编写 requirements.in直接依赖echo requests\nflask2.0 requirements.in# 2. 编译生成锁定文件 requirements.txtpip-compile requirements.in# 3. 同步安装pip-sync requirements.txt生成的那个txt文件里面包含了所有传递进来的那种依赖关系的精确的版本号, 这样一来就实现了可以重复进行的安装操作。2.第三步是针对项目管理层部分, 也就是那个英文简称为PDM的管理系统里面的第3.1小节内容, 这一小节专门来讨论一些关于依赖关系管理的技巧, 目的就是要做到一种优雅的状态。提供一个统一的由命令行接口来进行管理的方式, 这个管理动作涵盖了对依赖项的管理、对虚拟环境的管理、对构建环节的管理以及对发布操作的管理, 并且是把这个工具程序文件作为配置入口来使用。# pyproject.toml[tool.poetry]name my-projectversion 0.1.0description 示例项目authors [Your Name ][tool.poetry.dependencies]python ^3.9requests ^2.28flask { version ^2.3, extras [async] }[tool.poetry.group.dev.dependencies]pytest ^7.0black ^23.0[build-system]requires [poetry-core]build-backend poetry.core.masonry.api# 常用命令poetry new my-project # 创建新项目poetry add requests # 添加依赖poetry add --dev pytest # 添加开发依赖poetry lock # 锁定版本生成 poetry.lockpoetry install # 根据 lock 文件安装poetry build # 构建分发包poetry publish # 发布到 PyPI2.关于新的特性表现。2.3.2 PDM —— 追求标准化的现代工具PDM 这个东西, 它的一个核心的设计理念嘛, 就是说要严格地去遵循相关的标准, 同时又要很热情地拥抱未来那个东西。它是完全去基于 PEP 517 以及 PEP 621 这两个规范来实现的, 另外它还支持把解释器也弄到里面来作为项目依赖管理的一部分来处理。# pyproject.toml (PEP 621 标准格式)[project]name my-projectversion 0.1.0dependencies [requests2.28,flask[async]2.3,][project.optional-dependencies]dev [pytest7.0, black23.0][build-system]requires [pdm-backend]build-backend pdm.backend# 常用命令pdm init # 初始化项目pdm add requests # 添加依赖pdm add -d pytest # 添加开发依赖pdm lock # 生成 pdm.lockpdm install # 安装依赖pdm run python script.py # 在环境中运行pdm venv activate # 激活虚拟环境的独有的, 让人可以一眼就看清楚的好处在于:2.这是官方所推荐的那个关于开发工作流的流程。由 Reitz 库作者创建旨在将 pip 和 venv 整合为统一工具并引入 和 .lock 。# Pipfile[[source]]url https://pypi.org/simpleverify_ssl truename pypi[packages]requests *flask 2.0[dev-packages]pytest *[requires]python_version 3.11# 常用命令pipenv install requests # 安装并加入 Pipfilepipenv install --dev pytest # 安装开发依赖pipenv lock # 生成 Pipfile.lockpipenv sync # 精确安装 lock 文件内容pipenv shell # 进入虚拟环境pipenv run python script.py # 运行命令特点有一个局限, 就是生态活跃度出现了下降的情况, 并且它也没有把精力集中在这个库的打包和发布工作上面。2.在第 4 层也就是科学计算层里, 会用到 Conda 或者 Mamba 这些工具。Conda 它是那个能够跨越不同语言来执行管理的工具, 它这种包管理器的能力, 不光是对那些包进行管控了, 它还可以同时去处理 C语言还有C语言的库文件, 也管得过来 R语言里的相关情况, 甚至还把系统里的那些工具都包含到它的管理范围之中去了, 所以说它这个特点呢, 是非常非常契合在数据科学这样的一种场景里面去使用的。# 创建环境conda create -n myenv python3.11 numpy pandas# 激活环境conda activate myenv# 安装包可指定频道conda install -c conda-forge pytorch# 导出环境conda env export environment.yaml# 从文件创建conda env create -f environment.yamlConda vs. pip 关键差异 最佳实践 2.这是关于第5点的内容, 也就是新星项目中的 uv 工具, 它是一个用极速的 Rust 语言实现的东西。uv 是由 Ruff 的作者所在的公司使用 Rust 编程语言开发的一个全能类型的工具, 其目标是要去替代 pip 以及 pip-tools, 甚至还包括其他相关的包管理工具。# 安装pip install uv# 创建虚拟环境uv venv# 安装依赖uv pip install requests# 依赖锁定uv pip compile requirements.in requirements.txt# 同步环境uv pip sync requirements.txt在对性能进行对比分析的时候可以发现, 使用uv这个方法比起使用pip来说, 它的速度会快上10到100倍那么多, 这主要是因为uv这个工具里面用到了Rust语言所擅长的并行下载功能, 还有那个非常高效的解析算法作为支撑。3. 把各个工具的对比情况整理出来, 并且写好了选型指南, 具体的编号是3.1章节, 里面放置了核心特性的对比表格, 然后是3.2章节, 里面有详细的选型决策树。3.第3节对于关键知识点进行的深度解析, 其第3.3.1部分专门讲了锁文件这个事物, 到底是有怎样一种作用, 同时它的格式又是长什么样子的。它是通过去锁定住锁文件, 也就是lock file的这个东西, 把所有的依赖, 包括那些传递性依赖的, 精确版本给限定死了, 来确保每次在进行安装工作的时候, 其环境能够达到完全一致的状态。2.现在已经是3 的阶段了, 并且这一版本也提供了对 PEP 751 格式进行导出的支持功能。3.作为新一代的项目配置标准, 我们需要去关注3.2版本的toml。PEP 517 以及 PEP 518, 还有那个 PEP 621 它们共同推动了这样一件事儿, 也就是 .toml 这种格式的文件, 它现在是变成了一个项目配置里面用来统一进入的那个入口。[build-system] # 构建系统声明requires [setuptools61.0, wheel]build-backend setuptools.build_meta[project] # 项目元数据 (PEP 621)name exampleversion 0.1.0dependencies [requests2.0][tool.black] # 工具专属配置line-length 883.3.3 依赖分组的最佳实践现在的这些用于管理项目方面的工具, 它们都是支持的, 就是支持把那些依赖给它按照具体的用途进行分组, 然后放到一起:# Poetry 风格[tool.poetry.group.dev.dependencies]pytest ^7.0[tool.poetry.group.docs.dependencies]sphinx ^5.0# PDM 风格 (PEP 735)[project.optional-dependencies]dev [pytest7.0]docs [sphinx5.0]test [pytest-cov4.0]3.在3.4章节所描述的内容里, 我们需要强调的一点是, 当处于高性能计算这一种特定的环境之中的时候, 千万不要去执行那个名为conda init的操作命令。在 HPC 这些共享的环境里, 一定要记住是决不能执行 conda init 去修改那个~/.文件的, 而是要通过使用模块加载也就是 load这种方式来激活对应的环境。# 正确做法module load anacondaconda activate myenv# 错误做法conda init # 会污染 shell 配置4. 在实打战的场合下, 推荐大家把目光放在这个场景上面, 也就是去做一个能够直接拿出来发布并且使用的库。向大家进行推荐, 这个工具就是PDM。# Poetry 创建库项目poetry new my-libcd my-libpoetry add --dev pytest pytest-cov# 编写代码...poetry buildpoetry publish场景二Web 应用/微服务开发推荐你选用这种工具。或者采用叫做pip-tools的这套方案。# pip-tools 工作流echo flask\nrequests\npsycopg2-binary requirements.inpip-compilepip-sync现在是第三个场景, 这个场景主要是关于数据科学或者是机器学习的一个项目方面。为大家提供这样一个工具方面的推荐, 那就是使用Conda这个软件以及配合着conda-forge这一个仓库。# 最佳实践conda create -n ml-project python3.11conda activate ml-projectconda install -c conda-forge numpy pandas pytorch jupyter# 对于 PyPI-only 包最后用 pippip install transformers datasets就是第四种情况, 也就是持续集成还有持续交付那个模式, 加上容器化进行部署的过程。关于推荐工具这回事, 你可以把目光投向, 当然啦, 也是一个非常值得去推荐的选项。# Dockerfile 示例FROM python:3.11-slimCOPY requirements.txt .RUN pip install -r requirements.txt # 或使用 uv5. 总结包管理工具并没有绝对的所谓的“最好”这一说, 只有那些针对各种不同场景来说是最为合适的那些情况。关键建议不管你是选择哪一个工具, 都一定要把锁文件交上去放到版本库里面去, 好让环境是可以被重复搞出来的。对于新做的这种项目来说, 是特别强烈推荐使用那个 dot toml 这种东西来作为配置的标准方案的。在做数据科学方面项目的时候, 默认情况下是要使用 Conda 这个工具的, 只有在必须这么做并且非常小心谨慎的那种时候, 才可能会混着用一下 pip 这个工具。你要去关注 PEP 751 这个标准的锁文件, 说不定在将来, 不同的工具之间能够互通的这个事情就有可能会真的变成现实。随着相关的生态持续处于标准化的阶段, 工具与工具之间的相互差异正在变得越来越小, 与此同时, 底层的性能表现方面以及标准化程度方面将成为未来的竞争核心所在, 比如这里提到的 uv 代表的底层性能以及 PDM 代表的标准化程度这样的例子, 只有把每一个工具背后所蕴含的设计哲学含义给弄清楚, 同时也明确它们的适用边界范围在什么地方, 才有可能使得工作过程达到游刃有余的状态。我们希望这篇文章能帮助你弄清楚关于包管理的工具之间的关系和线索, 从而在实际的项目里面去做那些明智的技术选择。同时我们也欢迎你到评论的区域里去分享你自己的使用经验以及对这方面的见解。