前言:Python 作为一个优秀的解释型语言,在各个计算机细分领域都有着广泛的运用。然而 Python 不同版本之间的细微不兼容性使得 Python 解释器、第三方库的管理变得很困难。

为了解决依赖问题,Python 社区发明了许多方法。本文从三个角度出发,试图解释清楚新手面对 Python 虚拟环境时经常遇到的困惑。

  • Python 虚拟环境是如何创建的
  • 系统如何识别并套用某个虚拟环境
  • Python 虚拟环境有哪些实现且推荐使用哪个虚拟环境管理器

1. 如何理解 Python 虚拟环境·

现阶段有若干个 Python 虚拟环境生命周期管理方法,这里列举较为常用的两种:

Python 虚拟环境(Isolated Environment)看似神秘,实则非常简单,它的存在依赖于两种 Linux 对象:

  • PATH 环境变量
  • 独立的 Python 虚拟环境对应的目录

使用下列命令可以输出环境变量:

1
echo $PATH | sed 's/:/\n/g' | sort

查看不同虚拟环境下 PATH 环境变量的差异。

fuzzware 虚拟环境:

1
2
3
4
5
6
/bin
/home/newton/Applications/miniconda3/condabin
/home/newton/Applications/miniconda3/envs/fuzzware/bin <---- 看这里
/usr/bin
/usr/games
/usr/local/bin

angr 虚拟环境:

1
2
3
4
5
6
/bin
/home/newton/Applications/miniconda3/condabin
/home/newton/Applications/miniconda3/envs/angr/bin <---- 看这里
/usr/bin
/usr/games
/usr/local/bin

可以明显发现, PATH 在 miniconda3 env 相关的行中,存在着与虚拟环境名相同的关键字。因此可得以下结论:Python 虚拟环境是在独立的文件夹里创建 Python 目录结构,执行虚拟环境切换时会将 PATH 中相关的字段替换为对应的虚拟环境路径。

下面是一个由 conda 创建的虚拟环境目录 layout,与 Linux 根目录布局很相似。

1
2
3
4
5
6
7
8
9
10
11
12
.
├── bin
├── compiler_compat
├── conda-meta
├── etc
├── include
├── lib
├── man
├── share
├── ssl
├── x86_64-conda_cos7-linux-gnu
└── x86_64-conda-linux-gnu

VSCode 在打开文件夹时,可以选择合适的 Python 虚拟环境。VSCode 有一套方法可以扫描系统安装的虚拟环境,但是我们之前说过,虚拟环境从存储上看就是一个文件夹,因此 VSCode 并不一定能扫描到全部的虚拟环境,必要时还得手动指定。不过使用 conda 一般没有问题。

2. 如何使用工具管理虚拟环境·

网上关于 conda 和 virtualenv 的对比已经有很详细的描述了,在此我们不再赘述。这里给出几个重要的观察:

  1. virtualenv 是一个 Python 虚拟环境管理工具,而 conda 不仅可以管理虚拟环境,还是一个高级的 Python 包管理器(与 pip 类似)。
  2. pip 可以管理 Python 的第三方库,但是不会对第三方库中的 binary 做兼容性检查,而 conda 的检查更为严格。
  3. conda 有自己的上游认证过的 Python 库,而且是二进制发行版,安装速度更快。
    • conda 尽管有无数优势,但是它的库并不全,有些冷门的 Python 库还是需要用 pip 安装,好在 pip 是装在虚拟环境的 bin 目录里,因此也可以在虚拟环境里使用用户态的 pip(不加 sudo)。

这里推荐不要安装 debian apt 仓库里的 Python,而是直接使用 conda 提供的 Python.

2.1 创建特定 Python 版本的虚拟环境·

使用 conda 命令创建 Python 3.6 版本的虚拟环境。

1
conda create -n <ENV_NAME> python=3.6

2.2 列出当前安装的虚拟环境·

1
conda env list

2.3 将当前虚拟环境导出为 requirements.txt 文件·

ENV_NAME 环境中包含的所有 Python 库导出为 requirements.txt 文件:

1
2
3
conda deactivate
conda activate <ENV_NAME>
conda list -e > requirements.txt

随后,可以使用如下命令重新安装所有的 Python 模块:

1
conda install --file requirements.txt

注意两点:

  • 不要在 base 环境里做任何安装,因为 base 环境在导出时会遇到一些麻烦。建议创建一个 normal 环境用来日常使用,无非就是在 .zshrc 里添加一行 conda activate normal 而已。
  • conda 导出的包里有可能包含 pip 安装的一些、conda 上游仓库中不存在的包,下次安装时 conda 找不到这种包,会直接报错,建议根据错误信息做修改。

3. 硬核观察·

用 conda 创建虚拟环境有时会非常快,因为 conda 使用硬链接机制索引 Python 组件中的重复文件,避免了从网上下载相同版本 Python 组件带来的存储开销。我们通过下面两张图的对比可以看到,在移除 test 虚拟环境之后,文件 x86_64-conda-linux-gnu-ld 的 inode 索引计数减少 1. 因此在不要求特定 Python 版本而只要求环境隔离的情况下,可以肆无忌惮地创建 Python 虚拟环境。

图 1. 在移除某个虚拟环境之前,x86_64-conda-linux-gnu-ld 文件的引用计数一直是 5,表示有五个文件引用了同一个磁盘中的入口,即这五个文件实际上是同一个文件。

图 2. 在移除之后,这些文件的引用计数减少了 1,说明确实是重复计数。创建多个虚拟环境,并不会造成磁盘空间的浪费。

总结·

通过深入理解 Python 虚拟环境的实现方式,有助于用户更容易地使用 conda 等工具创建自己的虚拟环境。通过导出虚拟环境,也可以实现良好的持久化存储。不过要注意,pipconda 可以共存,比如在某个虚拟环境里安装一些 conda 仓库里不存在的包,就需要用 pip 安装。此时 pip 会将包安装在虚拟环境里,而非安装在系统目录里。这时候再导出、导入就会发生问题,因为 requirements.txt 里有些包 conda 装不上,pip 能装但是版本不一定合适。因此建议,不要混用 pipconda,除非万不得已。