Python虚拟环境
Contents
虚拟环境为运行Python项目的理想环境,可以有效的与系统Python环境进行隔离,每个项目都可以有一个干净的环境运行。
但是对于国内一些企业,客户现场环境复杂,甚至无法连接外网。对于这种场景,任何虚拟环境创建的手段都无法实现。
虽然 Pyinstaller 可以实现,但跨多个 Linux 操作系统,想要实现 “一包通用",仍没有办法实现。
Python虚拟环境创建
Python虚拟环境的创建,按照Python3的方式,通过 venv
模块进行创建。
|
|
参数解析:
--system-site-packages
- 在虚拟环境中安装第三方模块时,是否也在系统环境中安装--symlinks
- 使用链接的方式,软链系统环境的Python解释器。默认情况下使用软链的方式--copies
- 使用拷贝的方式,拷贝系统的Python解释器作为虚拟环境的解释器1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
# python3 -m venv --symlinks /usr/local/python_env/3.8.6/alfons # tree ./alfons/bin ./alfons/bin ├── Activate.ps1 ├── activate ├── activate.csh ├── activate.fish ├── easy_install ├── easy_install-3.8 ├── pip ├── pip3 ├── pip3.8 ├── python -> python3 └── python3 -> /usr/local/bin/python3 0 directories, 11 files # python3 -m venv --copies /usr/local/python_env/3.8.6/alfons_copy # tree ./alfons_copy/bin ./alfons_copy/bin ├── Activate.ps1 ├── activate ├── activate.csh ├── activate.fish ├── easy_install ├── easy_install-3.8 ├── pip ├── pip3 ├── pip3.8 ├── python └── python3 0 directories, 11 files
--clear
- 在创建虚拟环境前清理已经存在的目录,不能与--upgrade
一同使用--upgrade
- 升级环境中已经存在的内容,不能与--clear
一同使用1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
# ll alfons total 0 -rw-r--r-- 1 alfons wheel 0B 1 12 20:17 ok # python3 -m venv --upgrade /usr/local/python_env/3.8.6/alfons # ll alfons total 8 drwxr-xr-x 9 alfons wheel 288B 1 12 20:17 bin drwxr-xr-x 2 alfons wheel 64B 1 12 20:17 include drwxr-xr-x 3 alfons wheel 96B 1 12 20:17 lib -rw-r--r-- 1 alfons wheel 0B 1 12 20:17 ok -rw-r--r-- 1 alfons wheel 75B 1 12 20:17 pyvenv.cfg # python3 -m venv --clear /usr/local/python_env/3.8.6/alfons # ll alfons total 8 drwxr-xr-x 13 alfons wheel 416B 1 12 20:18 bin drwxr-xr-x 2 alfons wheel 64B 1 12 20:18 include drwxr-xr-x 3 alfons wheel 96B 1 12 20:18 lib -rw-r--r-- 1 alfons wheel 75B 1 12 20:18 pyvenv.cfg
--without-pip
- 不在虚拟环境中安装pip1 2 3 4 5 6 7 8 9
# python3 -m venv --without-pip /usr/local/python_env/3.8.6/alfons # tree ./alfons/bin ./alfons/bin ├── Activate.ps1 ├── activate ├── activate.csh ├── activate.fish ├── python -> python3 └── python3 -> /usr/local/bin/python3
--prompt PROMPT
- 指定运行虚拟环境的前缀提示,默认情况下,提示为环境路径1 2 3 4 5 6 7
# python3 -m venv alfons_no # source alfons_no/bin/activate (alfons_no) # # python3 -m venv --prompt nihao alfons # source alfons/bin/activate (nihao) #
Python虚拟环境与系统环境产生隔离的原理
Python虚拟环境的关键在于 activate
文件,里面 初始化了虚拟环境运行时的环境变量,指定了 Python解释器的搜索路径 PATH
。
|
|
同时,activate 文件中也可以通过控制环境变量 PYTHONHOME
、PYTHONPATH
来重新定义Python运行时模块搜索路径。
- PYTHONHOME - 指定Python标准库路径(
/usr/local/python_env/3.8.6/main/lib/python3.8/
) - PYTHONPATH - 指定Python模块搜索路径(
/usr/local/python_env/3.8.6/alfons/lib/python3.8/site-packages/
)
|
|
虚拟环境中使用的Python模块搜索路径优先排序如下:
- /usr/local/lib/python3.8 - 系统标准库搜索路径,该路径下保存了标准库文件
- /usr/local/lib/python3.8/lib-dynload - 保存模块用到的动态库,Python有些模块使用c语言编写
- /usr/local/lib/python3.8/site-packages - 系统环境保存第三方库的路径
- /usr/local/python_env/3.8.6/alfons/lib/python3.8/site-packages -
虚拟环境
保存第三方库的路径
|
|
可以看到,即使我们使用的是Python虚拟环境,但是仍然依赖了系统Python主环境的模块。
通过上面的现象,我们可以得出一个结论:
Python虚拟环境的制作是为了满足 本地系统环境中的Python三方库不受污染,而不是为了迁移到其他机器也能使用。
下面是直接迁移Python虚拟环境(直接拷贝 /usr/local/python_env/3.8.6/alfons
下所有文件至安装机器上)的现象,提示系统模块未找到:
|
|
原因就是我们没有同时迁移 /usr/local/lib/python3.8
目录下的主环境
进击 - 如何创建一个不依赖系统环境的Python虚拟环境?
想要创建一个不依赖系统Python环境的虚拟环境,那就需要 同时迁移系统环境和虚拟环境。具体步骤如下:
下载Python源码,编译安装Python
- 指定Python主环境的安装路径,默认情况下为
/usr/local/bin/python
- 编译配置时,使用
--prefix
参数指定安装路径 - 编译主环境
- 安装主环境
1 2 3 4 5
# tar -xf Python-3.8.6.tgz # cd Python-3.8.6 # ./configure --prefix=/usr/local/python_env/3.8.6/main --disable-option-checking --enable-shared --enable-loadable-sqlite-extensions # make -j "$(nproc)" # make install
- 指定Python主环境的安装路径,默认情况下为
制作虚拟环境
1 2
# export LD_LIBRARY_PATH=/usr/local/python_env/3.8.6/main/lib:$LD_LIBRARY_PATH # /usr/local/python_env/3.8.6/main/bin/python3 -m venv --symlinks --prompt alfons /usr/local/python_env/3.8.6/alfons
在 activate 文件中添加动态库搜索路径
LD_LIBRARY_PATH
1
# echo "export LD_LIBRARY_PATH=/usr/local/python_env/3.8.6/main/lib:$LD_LIBRARY_PATH" >> /usr/local/python_env/3.8.6/alfons/bin/activate
打包 Python主环境 和 虚拟环境
但是,将 Python主环境 和 虚拟环境 通过这种方式部署后,仍然会出现其他的问题:动态库(*.so)找不到。
解决Python虚拟环境 *.so 依赖问题
首先在解决这个问题之前,我们要明确一点:Linux 中,不管是静态链接还是动态加载,在运行时,所依赖的库文件的搜索路径都是按照一定顺序来查找的
- 「1」编译目标代码时指定的动态库搜索路径 rpath,编译过程中指定
- 「2」环境变量 LD_LIBRARY_PATH 指定的动态库搜索路径,运行时指定
- 「3」配置文件 /etc/ld.so.conf 中指定的动态库搜索路径
- 「4」默认的动态库搜索路径 /lib
- 「5」默认的动态库搜索路径 /usr/lib
「不能解决问题」方法一:拷贝系统动态库,改变LD_LIBRARY_PATH
- 查询标准库so文件依赖的系统动态库路径
- 将依赖的系统动态库路径拷贝至
/usr/local/python_env/system_lib
下 - 修改虚拟环境中的
LD_LIBRARY_PATH
参数,按照下面的顺序进行查找/usr/local/python_env/3.8.6/main/lib
- 查找libpython.so
/lib、/lib64、/usr/lib
- 系统动态库路径/usr/local/python_env/system_lib
- 拷贝的系统动态库路径
解决思路是,虚拟环境会 先在系统中查找动态库,如果没有找到,转至拷贝的系统动态库路径进行查找。
这种方式可能会有不足之处,新打好的包,可能会出现 glibc 版本不兼容的情况。原因在于,编译环境中的 glibc版本和运行环境中的glibc版本 不一样。
|
|
为什么不先在 拷贝的系统动态库路径进行查找? 按照下面的顺序进行查找
/usr/local/python_env/3.8.6/main/lib
- 查找libpython.so
/usr/local/python_env/system_lib
- 拷贝的系统动态库路径/lib、/lib64、/usr/lib
- 系统动态库路径如果设置 拷贝的系统动态库路径 优先,会出现 在Python代码中,如果需要运行系统命令,由于设置的 LD_LIBRARY_PATH 环境变量关系,系统命令对应的依赖,首先从 拷贝的系统动态库路径 查找,仍然会出现 glic 版本不一致的问题
方法二:修改环境中可执行程序和依赖动态库的 RUNPATH(rpath)
使用 patchelf 修改编译后的动态库或可执行程序的 RUNPATH
和 ld-linux.so
程序的路径。
- 修改动态库的
runpath
-patchelf --set-rpath {new_rpath} {so}
- 修改动态库的
ld-linux.so
链接器路径 -patchelf --set-interpreter {new_ld_so_path} {so}
通过此方法修改后,动态库首先会在 rpath
路径下查找指定的动态库,就不牵扯到 LD_LIBRARY_PATH 环境变量的问题。
|
|
后记:如何减小虚拟环境体积?
制作后的 虚拟环境+主环境 体积有 203M
,主环境便有 189M
!由于已经使用了动态库、链接的方式,在Python解释器上已经没有过多的操作空间。
|
|
查看后,发现主环境中有许多的 测试代码、exe文件、doc文件,这些文件对于Linux运行环境来说是非必要的,可以去除。
|
|
删减掉这些不必要的文件后,虚拟环境 + 主环境 体积 89M
。
总结
- Python虚拟环境,主要的目的是解决 项目环境和系统环境隔离,而不是以迁移至其他主机为目的。
- Python虚拟环境的主要实现原理是,通过
activate文件
重新定义运行时环境变量。 - Python3开始,主要使用系统自带的
venv
模块进行虚拟环境的制作。 - 通过 修改可执行程序和依赖的动态库 的rpath,可以使虚拟环境完全独立于操作系统
- 主环境中的 测试文件、doc文件 可以去除,不影响程序运行。