
刚开始用 Python 并尝试创建自己的包时,我曾经很困惑——打包和依赖管理似乎比预想的复杂得多。况且市面上的工具实在太多,根本不知道哪一个才适合自己。相信不少人都有过同样的迷茫。这篇文章就来系统地梳理和对比一下 Python 生态里的环境与包管理工具,帮你看清每个工具的定位。
工具分类
我先把跟环境和包管理相关的功能分成五个类别:
- Python 版本管理
- 包管理
- 环境管理(主要指虚拟环境)
- 包构建
- 包发布
下面的维恩图直观地展示了各类工具之间的交集,有些工具是单一用途的,有些则是“多面手”:

我们从开发者的视角挨个看这几个类别。假设你同时在做个人项目和工作项目:工作上要求使用 Python 3.7,个人项目却想用最新的 Python 3.11。换句话说,你需要能够安装不同的 Python 版本并在它们之间切换,这就涉及 Python 版本管理。
项目里几乎总会用到第三方包,比如数据科学领域常用的 pandas、scikit‑learn。这些依赖必须安装和管理(升级等),这就是包管理要做的事情。
由于不同项目可能依赖同一个包的不同版本,我们需要创建和管理(虚拟)环境来避免依赖冲突。大多数工具使用虚拟环境,少数则使用被称为“本地包”(Local Packages)的概念,后面会聊到。
当你准备与他人共享代码时,首先要构建包,然后才能发布到 PyPI 或其他索引上。
接下来,我们逐一深入每个类别,并重点介绍其中最主要的工具。
Python 版本管理
定义
Python 版本管理工具能够让你方便地安装多个 Python 版本,并在它们之间自由切换。
为什么需要?
原因很多。例如你可能并行维护多个项目,而每个项目要求的 Python 版本不同;也可能你的库要兼容多个 Python 版本,需要在各版本下测试;又或者你只是想尝鲜新版本特性,甚至测试预发布版本。
工具
维恩图里涉及版本管理的有:pyenv、conda、rye 和 PyFlow。我们先看唯一的单一用途工具 pyenv,多用途工具稍后再说。

Python 自带一个纯粹做版本管理的工具:pyenv,用起来相当简单。核心命令如下:
# 安装特定版本的 Python
pyenv install 3.10.4
# 在不同的 Python 版本之间切换
# 仅针对当前 shell 切换 Python 版本
pyenv shell <version>
# 在这个目录下,自动使用某个 Python 版本
pyenv local <version>
# 针对当前用户,全局设定 Python 版本
pyenv global <version>
(虚拟)环境管理
定义
环境管理工具可以创建和管理(虚拟)环境,让你为不同项目隔离依赖。
为什么需要?
前面说了,不同项目依赖不同的包版本,直接通过 pip install 把包安装到系统全局很容易引发冲突。虽然 --user 参数可以缓解,但很多初学者并不了解。因此,专业的虚拟环境管理工具就非常必要了。
工具
能够管理环境的工具有:venv、virtualenv、pipenv、conda、pdm、poetry、hatch、rye、PyFlow。其中只有 venv 和 virtualenv 是单一用途的,我们先看这两个。

venv
venv 是 Python 内置的虚拟环境创建包。命令很直观:
# 创建一个新的环境
python3 -m venv <env_name>
# 激活环境
. <env_name>/bin/activate
# 退出环境
deactivate
virtualenv
virtualenv 是 venv 的增强版,功能更多、速度更快。常用命令与 venv 几乎一样:
# 创建新环境
virtualenv <env_name>
# 激活环境
. <env_name>/bin/activate
# 退出环境
deactivate
包管理
在聊包管理前,有必要先了解最重要的配置文件:pyproject.toml。
pyproject.toml
Python 的打包机制经历了巨大改进。在 PEP 518 之前,主要依靠 setup.py 和 setuptools。如今,pyproject.toml 成了定义项目构建、元数据以及依赖等信息的标准文件。来看看 pandas 的 pyproject.toml 片段(完整文件见 github.com/pandas-dev/pandas):
[build-system]
# Minimum requirements for the build system to execute.
# See https://github.com/scipy/scipy/pull/12940 for the AIX issue.
requires = [
"meson-python==0.13.1",
"meson==1.2.1",
"wheel",
"Cython==3.0.5", # Note: sync with setup.py, environment.yml and asv.conf.json
# Any NumPy version should be fine for compiling. Users are unlikely
# to get a NumPy<1.25 so the result will be compatible with all relevant
# NumPy versions (if not it is presumably compatible with their version).
# Pin <2.0 for releases until tested against an RC. But explicitly allow
# testing the `.dev0` nightlies (which require the extra index).
"numpy>1.22.4,<=2.0.0.dev0",
"versioneer[toml]"
]
build-backend = "mesonpy"
[project]
name = 'pandas'
dynamic = [
'version'
]
description = 'Powerful data structures for data analysis, time series, and statistics'
readme = 'README.md'
authors = [
{ name = 'The Pandas Development Team', email='pandas-dev@python.org' },
]
license = {file = 'LICENSE'}
requires-python = '>=3.9'
dependencies = [
"numpy>=1.22.4; python_version<'3.11'",
"numpy>=1.23.2; python_version=='3.11'",
"numpy>=1.26.0; python_version>='3.12'",
"python-dateutil>=2.8.2",
"pytz>=2020.1",
"tzdata>=2022.7"
]
classifiers = [
# 省略后续
.lock 文件
另一个打包关键文件是锁定文件(如 xxx.lock)。pyproject.toml 中包含的是抽象依赖声明,而锁定文件记录的是具体安装的确切版本(比如 pandas==2.0.3),这样可以保证项目在不同环境下的可重复构建。以下是 Poetry 生成的 poetry.lock 片段(原文件:github.com/python-poetry/poetry):
# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
[[package]]
name = "build"
version = "1.0.3"
description = "A simple, correct Python build frontend"
optional = false
python-versions = ">= 3.7"
files = [
{file = "build-1.0.3-py3-none-any.whl", hash = "sha256:589bf99a67df7c9cf07ec0ac0e5e2ea5d4b37ac63301c4986d1acb126aa83f8f"},
{file = "build-1.0.3.tar.gz", hash = "sha256:538aab1b64f9828977f84bc63ae570b060a8ed1be419e7870b8b4fc5e6ea553b"},
]
[package.dependencies]
colorama = {version = "*", markers = "os_name == \"nt\""}
importlib-metadata = {version = ">=4.6", markers = "python_version < \"3.10\""}
packaging = ">=19.0"
pyproject_hooks = "*"
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
[package.extras]
docs = ["furo (>=2023.08.17)", "sphinx (>=7.0,<8.0)", "sphinx-argparse-cli (>=1.5)", "sphinx-autodoc-typehints (>=1.10)", "sphinx-issues (>=3.0.0)"]
test = ["filelock (>=3)", "pytest (>=6.2.4)", "pytest-cov (>=2.12)", "pytest-mock (>=2)", "pytest-rerunfailures (>=9.1)", "pytest-xdist (>=1.34)", "setuptools (>=42.0.0)", "setuptools (>=56.0.0)", "setuptools (>=67.8.0)", "wheel (>=0.36.0)"]
typing = ["importlib-metadata (>=5.1)", "mypy (>=1.5.0,<1.6.0)", "tomli", "typing-extensions (>=3.7.4.3)"]
virtualenv = ["virtualenv (>=20.0.35)"]
定义
包管理工具负责下载、安装项目依赖的库,并解决依赖关系。
动机
包(package)让我们可以通过 from package.module import my_function 这样的语法来组织代码,也方便共享。每个包在 pyproject.toml 中声明了自己的依赖,其他人只需通过这个文件就能一次性安装所有需要的包。
工具
能做包管理的工具不少:pip、pipx、pipenv、conda、pdm、poetry、rye、PyFlow。其中 pip 是 Python 开发生态中几乎人人会用的标准包管理器。

pip 随 Python 发行版一起提供,可以从 PyPI 和其他索引安装包。最熟悉的命令或许就是 pip install <package_name> 了。
多用途工具
下面重点介绍那些跨多个类别的工具。
pipenv
光看名字就知道,pipenv 把 pip 和 virtualenv 结合了起来。它可以同时完成虚拟环境管理和包管理:

pipenv 引入了两个额外文件:
Pipfile:一个 toml 文件,类似 pyproject.toml,用于声明项目的依赖。
Pipfile.lock:锁定文件,保证确定性构建,不再需要手动维护 requirements.txt。
常用命令:
# 安装某个包
pipenv install <package_name>
# 在虚拟环境运行某个脚本
pipenv run <script_name.py>
# 激活虚拟环境
pipenv shell
Conda
Conda 是一个通用的包管理系统,不限于 Python 包。功能庞大,这里不展开,网上教程很多。唯一要提的是,虽然 Conda 也能构建和发布包,但它生成的是 Conda 包,与我们通常的 PyPI 包稍有不同,所以我没把它列入标准的包构建/发布类别。

特征评估
接下来我会从以下几个维度比较这些工具:
- 是否管理依赖?
- 是否解析/锁定依赖?
- 是否有干净的构建/发布流程?
- 是否支持插件?
- 是否支持 PEP 660(可编辑安装)?PEP 660 基于
pyproject.toml 的可编辑安装,开发库时很有用。
- 是否支持 PEP 621(项目元数据)?PEP 621 定义了如何在
pyproject.toml 中书写核心元数据。之所以加这个维度,是因为有个工具(稍后揭晓)目前还不支持该 PEP,而是自己另搞一套。
Flit
Flit 专注一件事:把纯 Python 包发布到 PyPI,不关心其他事情:
- Python 版本管理:❌
- 包管理:❌
- 环境管理:❌
- 构建包:✅
- 发布包:✅
维恩图里它只落在包构建和发布的重叠区域:

- 管理依赖?❌
- 解析/锁定依赖?❌
- 构建/发布流程?✅
- 允许插件?❌
- PEP 660?✅
- PEP 621?✅
核心命令:
# 创建新的 pyproject.toml
flit init
# 构建并发布
flit publish
Poetry
Poetry 是人气很高的工具。维恩图显示,除了 Python 版本管理之外,它几乎包揽一切:
- Python 版本管理:❌
- 包管理:✅
- 环境管理:✅
- 构建包:✅
- 发布包:✅

需要注意的是,Poetry 目前还不支持 PEP 621。GitHub 上的相关 issue 已经开了大约一年半,依然没有解决(github.com/python-poetry/poetry/issues/3332)。
- 管理依赖?✅
- 解析/锁定依赖?✅
- 构建/发布流程?✅
- 允许插件?✅
- PEP 660?✅
- PEP 621?❌
创建项目
# 创建目录结构和 pyproject.toml
poetry new <project_name>
# 交互式创建 pyproject.toml
poetry init
# 从 pyproject.toml 安装依赖
poetry install
依赖管理
# 添加依赖
poetry add <package_name>
# 显示所有依赖
poetry show --tree
# 激活虚拟环境
poetry shell
# 在虚拟环境运行脚本
poetry run python <script_name.py>
锁定文件
首次安装包时,Poetry 解析 pyproject.toml 中的依赖并下载最新版本,随后将所有包的确切版本写入 poetry.lock。建议把该文件提交到版本仓库,保证团队一致。升级依赖用:
poetry update
构建/发布
# 打包(生成 .tar.gz 和 .whl)
poetry build
# 发布到 PyPI
poetry publish
pdm
pdm 是一个相对较新的包和依赖管理器(始于 2019 年),受到 Poetry 和 PyFlow 的启发(PyFlow 已不再活跃,所以本文不深入)。它要求 Python 3.7+,一个显著区别是允许用户自行选择构建后端。此外,pdm 是除 PyFlow 外唯一实现了 PEP 582(本地包)的工具,不过该 PEP 最近已被拒绝。
维恩图上,pdm 紧挨着 Poetry,功能上也是除版本管理外全覆盖:

-
Python 版本管理:❌
-
包管理:✅
-
环境管理:✅
-
构建包:✅
-
发布包:✅
-
管理依赖?✅
-
解析/锁定依赖?✅
-
构建/发布流程?✅
-
允许插件?✅
-
PEP 660?✅
-
PEP 621?✅
创建项目
# 交互式创建 pyproject.toml
pdm init
# 从 pyproject.toml 安装包
pdm install
依赖管理
# 添加依赖
pdm add <package_name>
# 展示所有依赖
pdm list --graph
运行代码
没有 shell 命令,但可以直接用 run:
# 使用当前环境运行
pdm run python <script_name.py>
锁定文件
与 Poetry 类似,首次安装后生成 pdm.lock。更新依赖:
pdm update
构建/发布
# 打包
pdm build
# 发布到 PyPI
pdm publish
Hatch
Hatch 的特点:
- Python 版本管理:❌
- 包管理:❌
- 环境管理:✅
- 构建包:✅
- 发布包:✅
它的作者曾承诺会尽快加入锁定功能(github.com/pypa/hatch/issues/1030),建议留意最新版本是否已经实现。

- 管理依赖?❌
- 解析/锁定依赖?❌
- 构建/发布流程?✅
- 允许插件?✅
- PEP 660?✅
- PEP 621?✅
创建项目
# 创建目录结构及 pyproject.toml
hatch new <project_name>
# 交互式创建项目
hatch new -i <project_name>
# 初始化现有项目或创建 pyproject.toml
hatch new --init
依赖管理
没有 add 命令,依赖需要手动添加到 pyproject.toml。查看依赖用:
hatch dep show table
运行代码
# 激活虚拟环境
hatch shell
# 在虚拟环境运行脚本
hatch run python <script_name.py>
构建/发布
# 打包
hatch build
# 发布到 PyPI
hatch publish
声明式环境管理
Hatch 的一个独特功能是允许在 pyproject.toml 里直接配置虚拟环境,甚至为环境定义专属脚本,例如用于代码格式化(更多见官方文档)。
Rye
Rye 由 Flask 作者 Armin Ronacher 于 2023 年 5 月推出,设计上深受 Rust 的 rustup 和 cargo 的影响。它用 Rust 编写,覆盖了所有五个类别:

-
Python 版本管理:✅
-
包管理:✅
-
环境管理:✅
-
构建包:✅
-
发布包:✅
-
管理依赖?✅
-
解析/锁定依赖?✅
-
构建/发布流程?✅
-
允许插件?❌(未来可能添加)
-
PEP 660?✅
-
PEP 621?✅
创建项目
# 创建目录结构和 pyproject.toml
rye init <project_name>
# 指定 Python 版本
rye pin 3.10
依赖管理
# 添加依赖(尚未安装)
rye add <package_name>
# 同步虚拟环境、锁定文件等(此步骤安装依赖)
rye sync
运行代码
# 启动虚拟环境
rye shell
# 使用虚拟环境运行脚本
rye run python <script_name.py>
构建/发布
# 打包
rye build
# 发布到 PyPI
rye publish
总结
各工具的特征对比汇总如下:
| 工具 |
管理依赖 |
解析/锁定依赖 |
构建/发布流程 |
插件支持 |
PEP 660 |
PEP 621 |
| Flit |
❌ |
❌ |
✅ |
❌ |
✅ |
✅ |
| Poetry |
✅ |
✅ |
✅ |
✅ |
✅ |
❌ |
| pdm |
✅ |
✅ |
✅ |
✅ |
✅ |
✅ |
| Hatch |
❌ |
❌ |
✅ |
✅ |
✅ |
✅ |
| rye |
✅ |
✅ |
✅ |
❌ |
✅ |
✅ |
原文:https://alpopkes.com/posts/python/packaging_tools
译文:https://juejin.cn/post/7325611798135767090
译者:hashcon
如果你对 Python 工具选型仍有困惑,欢迎到 云栈社区 一起探讨。也别忘了查看 Python 官方文档里的更多细节,关于 Conda 的使用还可以参考官方的入门指南。