本文均以Linux为例。
复制结构与文件
当使用venv
创建虚拟环境时,模块会重建操作系统上标准Python安装的文件和文件夹结构,从而保证Python可以如预期一样隔离地工作,而无需额外的改变。Python还会将调用venv
模块时使用到的Python可执行文件的副本或符号链接也复制到该文件夹结构中:
venv/
│
├── bin/
│ ├── Activate.ps1
│ ├── activate
│ ├── activate.csh
│ ├── activate.fish
│ ├── pip
│ ├── pip3
│ ├── pip3.12
│ ├── python
│ ├── python3
│ └── python3.12
│
├── include/
│
├── lib/
│ │
│ └── python3.12/
│ │
│ └── site-packages/
│
├── lib64/
│ │
│ └── python3.12/
│ │
│ └── site-packages/
│
└── pyvenv.cfg
如果你找到系统安装Python的位置,并观察其目录结构,你会发现虚拟环境与其十分相似。
虚拟环境基于的基础Python安装在
pyvenv.cfg
文件中的home
键下可见。
调整前缀查找(Prefix-Finding)过程
有了标准的文件夹结构,只需根据 venv
规范对其前缀查找过程稍作调整,虚拟环境中的 Python 解释器就能了解所有相关文件的位置。
Python 解释器不是通过查找 os
模块来确定标准库的位置,而是首先查找 pyvenv.cfg
文件。如果解释器找到了这个文件,并且其中包含一个 home
关键字,那么解释器就会使用这个关键字来设置两个变量的值:
sys.base_prefix
将保存用于创建此虚拟环境的 Python 可执行文件的路径,您可以在pyvenv.cfg
中的home
关键字下定义的路径找到它。sys.prefix
将指向包含pyvenv.cfg
的目录。
如果解释器找不到 pyvenv.cfg
文件,那么它就会认为自己不是在虚拟环境中运行,这时 sys.base_prefix
和 sys.prefix
都会指向相同的路径。
可通过查看
sys.base_prefix
和sys.prefix
变量内容来验证该过程。>>> import sys >>> sys.base_prefix >>> sys.prefix
激活环境后:
>>> import sys >>> sys.base_prefix '/usr/local' >>> sys.prefix '/home/name/path/to/venv'
停用环境后:
>>> import sys >>> sys.base_prefix '/usr/local' >>> sys.prefix '/usr/local'
若上述两个变量值不同,则Python调整寻找模块的路径:
site
和sysconfig
标准库模块被修改以使标准库和头文件将相对于 sys.base_prefix […] 查找,而 site-package 目录 […] 仍相对于 sys.prefix […] 查找。
这一更改允许虚拟环境中的 Python 解释器使用基本 Python 安装中的标准库模块,同时指向其内部 site-packages 目录来安装和访问外部软件包。
链接回标准库
Python虚拟环境旨在提供一个轻量的提供隔离的Python环境的方法,从而可以快速地创建或删除。由此,venv
只复制最小化的必要文件。
虚拟环境中的 Python 可执行文件可以访问作为环境基础的 Python 安装的标准库模块。Python 通过在 pyvenv.cfg
中的 home
设置中指向基本 Python 可执行文件的文件路径来实现这一点:
home = /usr/local/bin
include-system-site-packages = false
...
Python 通过将相关路径添加到 sys.path 来查找标准库模块。在初始化过程中,Python 会自动导入 site
模块,并为该参数设置默认值。
Python 会话在 sys.path
中可以访问的路径决定了 Python 可以从哪些位置导入模块。
如果激活虚拟环境并输入 Python 解释器,则可以确认基本 Python 安装的标准库文件夹路径可用:
>>> import sys
>>> sys.path
['',
'/usr/local/lib/python312.zip',
'/usr/local/lib/python3.12',
'/usr/local/lib/python3.12/lib-dynload',
'/home/name/path/to/venv/lib/python3.12/site-packages']
因为包含标准库模块的目录路径在 sys.path
中可用,所以在虚拟环境中使用 Python 时,可以导入任何标准库模块。
修改PYTHONPATH
为确保使用虚拟环境中的 Python 解释器来运行的脚本,venv
会修改 PYTHONPATH 环境变量(可以使用 sys.path 访问该变量)。
若未激活任何虚拟环境,访问该变量,则会看到默认Python安装的路径:
>>> import sys
>>> sys.path
['',
'/usr/local/lib/python312.zip',
'/usr/local/lib/python3.12',
'/usr/local/lib/python3.12/lib-dynload',
'/usr/local/lib/python3.12/site-packages']
其中'/usr/local/lib/python3.12/site-packages'
为site-packages
目录,该目录包含了安装的外部包(如,使用pip
安装的包)。在没有激活虚拟环境的情况下,该目录嵌套在与 Python 可执行文件相同的文件夹结构中。
Windows 上的
Roaming
文件夹包含一个额外的site-packages
目录,该目录与使用pip
的--user
标志的安装相关。该文件夹提供了一定程度的虚拟化,但仍将所有--user
安装的软件包集中在一处。
若激活了虚拟环境,则sys.path
变量值改变:
>>> import sys
>>> sys.path
['',
'/usr/local/lib/python312.zip',
'/usr/local/lib/python3.12',
'/usr/local/lib/python3.12/lib-dynload',
'/home/name/path/to/venv/lib/python3.12/site-packages']
即Python使用虚拟环境下的site-package
目录路径更换了默认的路径,使得Python加载虚拟环境中的外部包。而且由于全局的site-packages
目录未被列出,故Python不会加载其中的模块。
在 Windows 系统中,Python 还会将虚拟环境的根文件夹路径添加到
sys.path
。
由此,Python实现了虚拟环境中的外部包隔离。
此外,可以在创建虚拟环境时传递一个参数,从而实现以只读方式访问基本 Python 安装的系统
site-packages
目录。
激活时改变Shell的PATH变量
虽然不是必须的,但出于一致性考虑,一般推荐在在虚拟环境中工作之前先激活虚拟环境:
$ source venv/bin/activate
(venv) $
具体使用的激活脚本取决于操作系统以及使用的shell。
如果深挖虚拟环境的目录结构,将发现其附带了一些不同的激活脚本:
venv/ │ ├── bin/ │ ├── Activate.ps1 │ ├── activate │ ├── activate.csh │ ├── activate.fish │ ├── pip │ ├── pip3 │ ├── pip3.12 │ ├── python │ ├── python3 │ └── python3.12 │ ├── include/ │ ├── lib/ │ ├── lib64/ │ └── pyvenv.cfg
这些激活脚本的作用是一样的。不同脚本用于适应不用的操作系统以及shell。
激活脚本有两个主要行为:
- 路径:将
VIRTUAL_ENV
变量的值设定为虚拟环境的根目录,并将其 Python 可执行文件的相对位置预置到 PATH 中; - 命令提示符:由于脚本更改了命令提示符(在前面添加环境名),因此您可以很快知道虚拟环境是否已激活。
这两项更改都是并非必要的小改动,纯粹是为了方便使用。
当停用虚拟环境后,shell将会把改变恢复回去。
可在任意位置通过绝对路径使用
如前面的章节所述,无需激活虚拟环境即可使用之。
当在shell中只提供了二进制文件的名字时,shell将在
PATH
记录的路径中搜寻拥有该名字的可执行文件,选出并运行第一个批判的文件。
激活脚本更改了PATH
变量以使shell首先在虚拟环境的二进制文件目录下搜寻可执行文件,从而使得用户可以仅用pip
或python
这个名字便运行虚拟环境中的相应文件。
如果不激活环境的话,则可以通过使用对应Python的绝对路径来运行虚拟环境中的任何脚本:
$ /home/name/path/to/venv/bin/python
这个与激活环境再用python
调用是等价的。