Python 包与环境管理简史:从混乱到优雅

Python 诞生三十余年,但在它的生态里,如何优雅地管理依赖和环境始终是一条漫长的探索之路。
从 2004 年 easy_install 的横空出世,到今天集大成的 uv,无数开发者在“依赖地狱”与“环境隔离”之间寻找平衡。
这篇文章想和你一起回顾过去二十年里 Python 依赖与环境管理工具的迭代,也分享我个人在这些工具之间摸索的心路历程。

自动包管理工具的先驱:easy_install

在一切规范化工具出现之前,Python 的包管理是相当原始的。开发者们需要把第三方库的源码下载下来,手动放到项目目录里。
为了解决自动安装包的问题,easy_install 应运而生。

2004 年:easy_install——从 0 到 1 的突破

easy_install 是 Python 包管理的早期自动化解决方案。它可以从 PyPI 自动下载并安装包,极大地简化了获取第三方库的流程。然而,它也存在很多问题:

  • 无法直接卸载:安装容易,卸载却需要手动删除文件。
  • 无依赖管理:它会安装包及其所有依赖,但无法记录或锁定这些依赖的精确版本,导致“依赖地狱”问题依然存在。
  • 容易冲突:多个项目依赖同一个库的不同版本时,easy_install 会陷入困境。

虚拟环境管理 + 更智能的包管理:virtualenv/venv,pip

2008 年:virtualenv——为每个项目建一个虚拟环境

virtualenv 的出现是一个里程碑。它创造性地提出可以为每个项目创建一个独立的、隔离的 Python 虚拟环境。在这个虚拟环境里,你可以随心所欲地安装任何版本的包,而不会影响到系统全局或其他项目。virtualenv 解决了项目间的环境冲突问题,为后续的工具发展奠定了基础。

2011 年:pip——更智能的包安装器

pip 作为 easy_install 的替代品逐渐成熟,并最终成为 Python 事实上的标准包安装器。它解决了 easy_install 的诸多痛点:

  • 可以一条命令卸载包

    pip uninstall package_name
    
  • 支持依赖管理:能够自动处理依赖关系。

  • 引入 requirements.txt:通过一个简单的文本文件,你可以记录项目所需的所有依赖及其精确版本。其他人拿到项目后,只需运行

    pip install -r requirements.txt
    

    就能复刻你的开发环境。

2012 年:venv——Python 内置的虚拟环境工具

venv 是 Python 3.3 引入的一个标准库模块,它提供了 virtualenv 的核心功能,但更加轻量和易用。你可以通过

python -m venv my_env

来创建一个新的虚拟环境,而无需额外安装任何东西。

早期版本的 venv 相比 virtualenv,缺少很多高级特性,因此很多开发人员仍然选择继续使用 virtualenv。但随着 Python 的发展,venv 也越来越成熟,目前已经取代了 virtualenv 成为了虚拟环境的事实性官方标准。

Python 解释器版本管理工具:pyenv

pip 和 virtualenv/venv 解决了“包”和“环境”的问题,但还有一个更底层的问题:Python 解释器本身呢?如果你的电脑系统自带了 Python 2.7,但你的新项目需要 Python 3.10,而另一个维护中的老项目又需要 Python 3.3,怎么办?手动安装、修改 PATH?太麻烦了。

2011 年:pyenv——掌控你的 Python 版本

pyenv 的出现,完美地填补了这个空白。它深受 Ruby 社区的 rbenv 启发,其核心思想非常巧妙:它并不真正地“安装”多个 Python 版本,而是在你的 PATH 路径最前面插入一个“垫片”(shim)。当你调用 python 命令时,这个垫片会根据你当前目录或全局的配置,智能地转发给对应版本的 Python 解释器。

通过

pyenv install 3.10.0

命令,你可以轻松安装任意版本的 Python(此处示例 3.10.0)

通过

pyenv local 3.8.5

命令,你可以为当前目录下的项目指定使用任意版本的 Python(此处示例 3.8.5)

pyenv 解决了 Python 解释器版本管理的难题,它与 pip 和 venv 形成了三件套:pyenv 负责管理解释器版本,venv 负责创建隔离环境,pip 负责安装依赖包

一体化工具的尝试:pipenv 和 poetry

pyenv + virtualenv + pip 虽然强大,但毕竟需要操作三个工具。开发者们开始向往一个能“一键搞定所有”的解决方案。于是,一系列“All-in-One”工具应运而生。

2017 年:Pipenv——官方推荐的“希望”

Pipenv 由 Requests 库的作者 Kenneth Reitz 主导开发,并一度被 Python 官方推荐为“用于 Python 项目的官方依赖管理工具”。它试图将 pip 和 venv 的功能合二为一。

它用 Pipfile 和 Pipfile.lock 替代了 requirements.txt,提供了更精确的依赖锁定和开发/生产环境的区分。

同时,Pipenv 在早期因为一些性能问题、锁文件生成缓慢以及偶尔的 Bug,社区评价褒贬不一,逐渐失去了一些光环。

Pipenv 本身不管理 Python 解释器版本。所以 pyenv 在管理不同 Python 版本的多个项目时仍是必需的工具。(笔者并没有真正使用过 pipenv 这个工具,根据网上的资料查询得出这个结论,不太确定是不是真的没有这个功能。如果有误,请不吝赐教)

2018 年:Poetry——更现代的依赖管理工具

几乎与 Pipenv 同期,Poetry 横空出世,并迅速赢得了大量开发者的心。

  • 它使用 pyproject.toml 作为唯一的配置文件,这是 PEP 518 和 PEP 621 所倡导的现代 Python 项目标准,非常简洁和强大。
  • 它的依赖解析算法和锁文件(poetry.lock)生成速度比 Pipenv 更快、更可靠。
  • 它集成了构建、打包和发布到 PyPI 的功能,真正实现了从开发到部署的全流程管理。

和 pipenv 一样,Poetry 自身不负责 Python 版本的管理,仍然需要搭配 pyenv 来管理 Python 解释器版本。(笔者并没有真正使用过 poetry 这个工具,根据网上的资料查询得出这个结论,不太确定是不是真的没有这个功能。如果有误,请不吝赐教)

另一条路:科学计算领域的王者——Conda

在主流 Python 社区为 pip 和 virtualenv 演进时,科学计算领域走出了另一条完全不同的道路。

2012 年:Conda——安装依赖、Python 版本管理全包了

Anaconda 是数据科学领域发展出的包管理 +Python 版本管理工具。而且它管理的不仅仅是 Python 包,还可以是 R 语言等其他语言的软件包。此外,为了提供开箱即用的机制,Anaconda 会默认安装许多常用的科学计算库,但这也导致了安装体积过大。因此,又推出了 miniconda 这个精简版,它只包含了最基本的 Python 和 conda 包管理器。对于 Python 开发人员来说,更多使用的还是 miniconda。

Anaconda 在安装时就会自带一个稳定版本的 Python,同时还可以通过 conda 命令直接安装其他版本的 Python。

conda create -n myenv python=3.9

这条命令,就可以创建一个包含 Python 3.9 的新环境。

Anaconda 可以不依赖 pip,直接通过 conda install 命令安装包。但是 conda 安装的包版本常常会和 pip 包冲突,而且 PyPI 上的很多包都没有 conda 的版本。

另外,Anaconda 使用 environment.yml 文件来描述环境,这个文件类似于 requirements.txt,但更加复杂。在 conda 中,通过 yaml 文件来创建环境的命令为:

conda env create -f environment.yml -n 环境名

但说实话能够一次性成功配置环境属于少数情况,一般都会遇到配置太慢、卡死、solving environment failed、PackageNotFound、以及 pip 爆出的一堆红字。

综合上述原因,在实际的工作流中,人们通常会使用 conda 创建一个干净的虚拟环境,然后再在这个虚拟环境中使用 pip 安装其他依赖。

conda create -n myenv python=3.9
conda activate myenv
pip install -r requirements.txt

这也是现在科研领域使用 Python 的主要方式,绝大多数的 Python 科研项目都是这样安装依赖的。另外一些老的软件开发项目也会使用这种方式。VSCode 对于 conda 的兼容性也很好,可以直接检测出 conda 管理的虚拟环境。

速度与整合的终极追求:uv 的时代

时间来到最近,一个名为 uv 的工具横空出世,以其惊人的速度和“大一统”的雄心,再次搅动了整个生态。

2023 年:uv ——极速的全面解决方案

uv 由 Astral(Ruff 的开发公司)发布,它是一个用 Rust 编写的、极其快速的 Python 包安装器,最初旨在成为 pip 的直接替代品。
使用

uv pip install package_name

命令,可以极大地提升安装速度。它的速度优势是颠覆性的,将依赖解析和安装的时间从分钟级缩短到了秒级。

但 uv 的目标远不止于作为一个快速的 pip 替代品,它正在迅速整合 Python 项目管理的所有核心能力:

  • 项目管理uv init 命令可以快速创建一个包含 pyproject.toml 的项目骨架。
  • 虚拟环境管理uv venv 可以创建和管理虚拟环境。
  • 依赖解析与安装uv adduv removeuv sync 等命令提供了原生的依赖安装体验
  • Python 版本管理:可以通过 uv python install 命令安装指定的 Python 版本

现在仅使用 uv 一个工具就可以实现原本需要 pip,venv,pyenv 三个工具才能实现的功能。而且相比 conda,其更符合 Python 标准,更加 Pythonic。

笔者个人使用经历和体会

前面这段是对历史的梳理,现在来谈一谈我个人使用 Python 依赖管理工具的经历和体会。

本科毕业后,我先从事了两年 NodeJS 开发的工作。我个人感觉相对来说 NodeJS 的版本兼容性应该比 Python 好不少吧?当时压根就没想到需要不同的 NodeJS 版本来管理不同项目的问题,用我电脑上安装的 NodeJS LTS(长期支持版)运行公司的各个项目都没有遇到兼容性问题。另外,NodeJS 中使用 npm 工具管理项目依赖包,每个项目就是一个独立的环境,也不会出现不同项目之前依赖包冲突的问题。虽然现在也出现了 nvm 这个管理不同 Nodejs 版本的工具,但这个工具只是锦上添花,在大多数情况下并不是必需的。

但读研以后开始使用 Python,整个情况就变了。不同版本的 Python 之间兼容性很差,只安装一个 Python 版本完全无法应对不同的项目,而且 pip 安装依赖在没有虚拟环境的情况下,默认是到全局环境,无法进行不同项目的环境隔离。

conda 是我最早接触的工具,因为我是读研后才开始使用 Python 的,一开始主要做的也是科研项目。实验室的师兄师姐教我们就是直接用 conda,而且基本上所有的科研论文项目的主页上也都是用的 conda。venv 作为 Python 官方的虚拟环境解决方案我也了解过,但还是不如 conda 方便,基本没用过。有了 conda 以后,项目的版本依赖管理就变得清晰了很多,而且在 conda 里面想用哪个版本的 Python 就可以用哪个版本的 Python。

conda 自带的 conda install 命令在很多时候安装不到想要的包,而且速度很慢。所以我都是先用 conda 安装虚拟环境,再用 pip 安装依赖。

假如我想导出环境给另一台电脑使用该怎么办呢?一开始使用的是这个命令:

pip freeze > requirements.txt

但是这样会存在一个问题,导出的文件包含的是当前虚拟环境中所有的包。有的时候为了图省事,一个 conda 虚拟环境会给多个项目使用,这样就可能包含很多用不着的包。为了解决这个问题,我又找到了 pipreqs 这个工具,该工具会扫描当前项目下实际使用的依赖包,并生成对应的 requirements.txt 文件。

尽管 pipreqs 工具可以导出实际需要的依赖,但仍然需要手动导出。但使用过 npm 的我还是觉得不太方便,为什么就不能在安装的同时直接生成一个类似于 package.json 的文件来管理依赖呢?(其实 pyproject.toml 文件早已出现,但由于读研期间把绝大多数的精力都放在读论文,想科研点子上,没有时间学习开发工具的使用,一直活在自己的信息茧房里,也就不知道这个东西的存在)

一直到了研究生毕业,再次工作以后,我接触了 Dify 项目,发现其中使用了一种新的依赖管理工具 uv,而且使用 pyproject.toml 管理项目依赖包。这不就是我梦寐以求的工具吗?再去回看历史,发现原来之前还有 poetry,pipenv 这些工具。而 uv 基本上解决了之前所有工具的问题。现在的 Python 开发人员真是身处一个好时代,有 uv 这么好的工具。

最后说一下工具的选型

对于科研人员来说,还是推荐使用 conda。尽管其比较笨重,但使用方便,而且也是科研领域的主流解决方案。

但对于开发人员来说,强烈建议转向 uv 这个现代化工具。这是目前 Python 依赖管理的集大成者。

  • Python

    Python 是一种面向对象、直译式电脑编程语言,具有近二十年的发展历史,成熟且稳定。它包含了一组完善而且容易理解的标准库,能够轻松完成很多常见的任务。它的语法简捷和清晰,尽量使用无异义的英语单词,与其它大多数程序设计语言使用大括号不一样,它使用缩进来定义语句块。

    561 引用 • 677 回帖 • 1 关注
  • pip
    5 引用 • 1 回帖
  • Conda
    2 引用 • 1 回帖
  • uv
    2 引用 • 1 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
  • uv 特别好用就,速度快,方便。我自己在公司里的 Linux 服务器上因为无法访问外网,所以用的 miniforge (类似于 conda)的集成环境,这样就不用手动编译安装 Python 了。