之前主要使用的都是conda的虚拟环境,图的是方便省事,但是始终无法理解虚拟环境的底层逻辑, 这里选择学习最简单的、也是python基础的venv虚拟环境。

venv 虚拟环境是Python提供的一个工具,用于创建隔离的环境来管理不同项目的依赖关系,每一个虚拟环境实际存储于一个目录下, 但是可以在任何其它位置激活和使用这个虚拟环境。

在Linux中即使有 Python,也可能不包括 venv,需要额外下载 python3-venv 。

创建虚拟环境

在当前目录下创建一个名为 myenv 的虚拟环境(但是按照惯例,虚拟环境的名称通常为venv.venv

1
python -m venv myenv

这实际会在当前目录下创建一组文件和子目录,通常包括以下几个关键部分:

  • bin/(或Scripts/):包含一组独立的Python解释器(可执行文件)和工具(如pip);
  • lib/pythonX.Y/site-packages/(或lib/site-packages/):存放该虚拟环境下所有的Python库,初始情况下只有pip。
  • include/:包含C语言头文件,用于在该虚拟环境中进行C扩展编译。
  • pyvenv.cfg:包含虚拟环境的配置信息(如Python版本信息,创建时的命令)。

这些目录结构对于Windows和Linux存在一些细微区别,涉及的命令脚本实际上也提供了bash,cmd和pwsh这几个版本,下面的讨论主要以Windows为例。

pyvenv.cfg大致包括如下内容:

1
2
3
4
5
home = D:\ProgramLang\miniconda3
include-system-site-packages = false
version = 3.12.1
executable = D:\ProgramLang\miniconda3\python.exe
command = D:\ProgramLang\miniconda3\python.exe -m venv D:\ProjectRoot\Test\TestPyPi\myenv

由于虚拟环境中可能存在很多信息是和绝对路径相关的(例如上面的pyvenv.cfg),所以最好不要直接将myenv/文件夹整体复制到其它地方进行使用,可能存在潜在的问题。

激活、退出虚拟环境

激活(当前目录下的)虚拟环境

1
2
3
4
5
# windows
.\myenv\Scripts\activate

# linux
source myenv/bin/activate

当然在其它目录下的虚拟环境也可以被激活和使用,只需要相应调整命令中的路径。

激活环境之后,shell的命令行提示符通常会变化,自动提示当前虚拟环境的名称,例如

1
(myenv) user@xxx$

使用deactivate命令可以退出当前的虚拟环境

1
deactivate

激活虚拟环境实际上就是调用了对应目录中的激活脚本,执行了一些简单操作,主要包括:

  • 修改环境变量PATH,将当前目录添加到环境遍历路径的开头,确保pythonpip等命令会优先找到当前虚拟环境中的实例;
  • 将当前环境的site-packages/目录添加到模块搜索路径中,确保可以成功导入当前环境中所安装的包;
  • 提供一个deactivate命令,作用就是将activate所做的修改撤销。

可以通过设置环境变量VIRTUAL_ENV_DISABLE_PROMPT来关闭虚拟环境对提示符的修改,例如

1
$Env:VIRTUAL_ENV_DISABLE_PROMPT = "true"

如果需要删除虚拟环境,只需要删除对应的目录即可。 这里的激活/退出虚拟环境操作都和conda有所不同,例如conda的退出命令是完整的conda deactivate

在虚拟环境中安装python包

在激活虚拟环境之后,可以使用pip安装依赖:

1
pip install <package_name>

注意:不激活虚拟环境时直接调用的pip是全局的,激活环境后调用的pip则是虚拟环境中的,激活与否会影响python包的实际安装位置。

例如默认情况下,site-packages/目录的内容如下:

1
2
3
site-packages/
|- pip/
|- pip-23.2.1.dist-info/

这里需要解释一下,pip本质上仍然是一个Python包,Scripts目录中的 pip.exe 只提供一个入口功能,实际还会调用site-packages/pip/目录中的内容。 pip-23.2.1.dist-info 目录用于存储与安装的 pip 包版本相关的元数据。

我们可以手动安装一个包,例如tqdm

1
pip install tqdm

这个命令会自动下载两个包:colorama, tqdm,前者是tqdm的必要依赖。 此时虚拟环境中发生了一些变化,site-packages/目录中增加了对应的目录:

1
2
3
4
5
6
7
site-packages/
|- pip/
|- pip-23.2.1.dist-info/
|- colorama/
|- colorama-0.4.6.dist-info/
|- tqdm/
|- tqdm-4.67.1.dist-info/

由于tqdm还提供了一个可执行文件,因此Scripts/目录中还添加了一个tqdm.exe,可以在命令行中直接调用。

导出依赖与恢复

我们可以导出当前虚拟环境的所有依赖,并保存到文件中,方便下次安装

1
pip freeze > requirements.txt

可以在其它位置通过该 requirements.txt 文件安装所有的依赖,从而恢复相同的虚拟环境

1
pip install -r requirements.txt

补充

除了通过 PyPi 进行安装之外,pip 还支持很多安装方式,主要包括:

  • 通过 Github 仓库安装:pip install git+https://github.com/user/repo.git;(要求该仓库的内容符合 Python 包的格式标准,例如有 setup.py 文件等)
  • 通过 .tar.gz 或 .zip 文件进行源码安装:pip install /path/to/package.tar.gz;(支持直接使用网络上的压缩包)(要求这些压缩包中的内容符合 Python 包的格式标准)
  • 使用 Python 设计的 wheel 文件(后缀 .whl)进行模块的安装,wheel 文件是被设计替代源码压缩包的二进制文件,它相比于压缩包的优势是已经包含了所有编译好的二进制文件,因此安装速度更快,并且可以避免一些编译问题。

pip 自身可能会尝试处理一些包之间的依赖关系,自动获取缺失的依赖,但是对于一些复杂的依赖关系,可能会出现一些问题,例如依赖的版本问题。 conda 也是一样的,它会自动管理各个包之间的依赖关系,在 conda 环境中混合使用 pip 进行安装时,更可能会产生一些冲突问题。

pip + venv 相比于 conda 有一些明显的优缺点:

  • 优势:pip 和 venv 的使用都比 conda 更加简单轻量级,conda环境还是太重了;
  • 缺点:venv 只能基于当前系统安装的 Python 版本来创建虚拟环境,不能切换 Python 版本;pip 只能管理 Python 包,不能管理其他语言的包。