Python 的虚拟环境用来创建一个相对独立的执行环境,尤其是一些依赖的三方包,最常见的如不同项目依赖同一个但是不同版本的三方包,而且,在虚拟环境中的安装包不会影响到系统的安装包。
不过,其具体的工作原理是怎样的,这里详细介绍。
简介
在开发 Python 程序时,经常会遇到要使用不同版本的问题,例如 Python 的 3.4、2.7 甚至是 2.6 版本,甚至是多个应用场景,例如机器学习、Web 框架等等,而通过 pip 安装时,一般只会安装到标准路径下,这会导致包特别混乱,而 Python 构建独立环境又特别方便,所以,这里介绍下如何使用独立的虚拟环境。
这里所谓的虚拟环境,保证了其 Python 可执行文件的版本独立,而且安装的包也在该环境对应目录下,所以也是相对独立的。
环境准备
在 Python 2 中,通常使用 pyenv 用于在多个不同系统 Python 版本之间切换,而使用 virtualenv 用于创建独立的开发环境。其中 pyenv 是一堆的 Bash 脚本,安装方式可以参考 github pyenv 中的介绍,而在 Linux 中,也可以通过上述的 alternatives 工具进行切换,所以,这里就不详细介绍了。
virtualenv
对于 Python 2 来说,可以通过 virtualenv 来为一个应用创建一套 “隔离” 的 Python 运行环境,详细可以参考 Guide to Python 中的详细介绍。
直接通过 pip install virtualenv
安装相应的最新版本,假设要开发一个新的项目,需要一套独立的 Python 运行环境,那么就可以按照如下步骤操作:
----- 创建目录
$ mkdir /tmp/project && cd /tmp/project
----- 创建一个独立的Python运行环境,命名为foobar
$ virtualenv --no-site-packages foobar
New python executable in /tmp/project/foobar/bin/python2
Also creating executable in /tmp/project/foobar/bin/python
Installing setuptools, pip, wheel...done.
通过命令 virtualenv
可以创建一个独立的 Python 运行环境,参数 --no-site-packages
表示已安装到系统环境中的所有第三方包都不会复制过来,这样,我们就得到了一个不带任何第三方包的 “干净” 的 Python 运行环境。
激活环境
新建的 Python 环境被放到当前目录下的 foobar 目录,此时可以通过 source foobar/bin/activate
切换到这个 Python 环境;当然,不同的终端如 csh、fish 需要执行不同的脚本。
此时的终端提示符会添加 (foobar)
前缀,然后可以通过 pip install jinja2
类似的命令安装三方包,需要退出时执行 deactivate
命令。注意,默认会使用 Python2 的版本,可以通过 --python=python3.6
参数指定版本号。
基本原理
所谓的独立环境,无非就是解决两个问题:A) 执行 Python 解析器所使用的版本;B) 使用独立的包。其中前者,在 Linux 主要是通过 PATH
环境变量设置,在 activate
脚本中有如下的内容。
VIRTUAL_ENV="/tmp/project/foobar"
export VIRTUAL_ENV
_OLD_VIRTUAL_PATH="$PATH"
PATH="$VIRTUAL_ENV/bin:$PATH"
export PATH
也就是将创建的目录添加到 PATH
环境变量最开始,那么就会优先查找该路径,这样就解决了 python 解析器独立的问题,另外就是下面的包查找的顺序了。
venv
在 Python 3.3 以上的版本,在基础安装包中已经集成了基础的虚拟环境创建工具 venv,这样可以用标准方式代替之前的 virtualenv 工具。
----- 在子目录下创建名称为machine的虚拟环境
$ python -m venv machine
有些参数可以通过 python -m venv -h
查看详细的帮助信息,接着是要激活创建的环境。
----- 对于Linux、Unix、Mac等操作系统
$ source machine/bin/activate
----- 如果是在Windows CMD中
D:> machine/Scripts/activate.bat
----- 如果在Windows PowerShell下
PS D:> machine/Scripts/Activate.ps1
在激活虚拟环境后,命令行会提示当前虚拟环境的名称。
配置文件
如上,如果是 Python3 就可以直接使用内置的 venv
模块,其原理与上述的相同,同时通过 pyvenv.cfg
配置文件来标识原始的 home 位置,该文件的内容如下。
home = /usr/bin
include-system-site-packages = false
version = 3.6.8
如果 include-system-site-packages
为 true
,解释器启动时就会将系统的库添加到 sys.path
里面,这样在虚拟环境就可以 import
系统中安装的包了。
注意,Python3 提供的 venv
模块只能根据当前版本创建,不能支持 Python2 。
包查找顺序
如果要使用独立的包,那么关键就是如何在通过 import
导入时查找到所需的包。
包的查找顺序可以查看 Python 模块简介 中的介绍,简单来说,就是先查看是否是内置模块,然后再从 sys.path
列表指定的地址中搜索。所以,这里的关键就是 sys.path
列表的生成。
关于 sys.prefix
在 Python 启动时,会先加载一个强依赖的 os.py
包,而查找这个包是根据解析器的当前路径,以及固定的查找规则来实现的。
简单来说,就是在当前路径加上 lib/python${VERSION}/os.py
逐层向上查找,注意,如果是 64 位的操作系统,那么会使用 lib64
替换掉之前的 lib
路径。
例如,默认的 Python3 的解析器路径为 /usr/bin/python3.6
,那么基础路径是 /usr/bin/
,所以,其查找顺序为。
/usr/bin/lib64/python3.6/os.py
/usr/lib64/python3.6/os.py
/lib64/python3.6/os.py
只要在任意路径上找到 os.py
包,那么就会退出查找,并设置好 sys.prefix
变量,详细可以通过 strace python
查看,会有如下的搜索路径。
stat("/usr/bin/Modules/Setup", 0x7fffb7146300) = -1 ENOENT (No such file or directory)
stat("/usr/bin/lib64/python2.7/os.py", 0x7fffb71462f0) = -1 ENOENT (No such file or directory)
stat("/usr/bin/lib64/python2.7/os.pyc", 0x7fffb71462f0) = -1 ENOENT (No such file or directory)
stat("/usr/lib64/python2.7/os.py", {st_mode=S_IFREG|0644, st_size=25910, ...}) = 0
在查找到 os.py
之后,会将该路径设置为 sys.prefix
变量,然后解析器就会到 ${sys.prefix}/lib/python${VERSION}
目录下查找包。
总结
那么其工作原理就是,将 python 解析器保存在 ${VENV_PATH}/bin/python
,然后创建 ${VENV_PATH}/lib/python${VERSION}
目录,并将相关的文件复制到该目录下,可以复制文件,也可以使用软连接。
参考
- Virtualenv 官方文档,细节可以参考该文档。
- Creation of virtual environments Python3 提供的 venv 介绍,包括常见参数以及配置文件。