简单介绍virtualenv的使用和原理
本文在centos7.6系统上实践
1. 快速体验virtualenv
1.1 安装virtualenv
可以通过pip或者yum安装virtualenv,推荐使用pip安装,因为pip安装版本较新,老版本有些参数已经在新版本中废弃
pip安装virtualenv
# pip install virtualenv
yum安装virtualenv
# yum install python-virtualenv
1.2 初始化虚拟环境,其中testenv
可以任意指定
# virtualenv testenv
1.3 进入该虚拟环境
当提示符前显示(testenv)即进入虚拟环境,后面在任意目录执行python命令执行的是该虚拟环境里面的python,同时通过pip安装的包也会安装到该虚拟环境里面
# source testenv/bin/activate
(testenv) [root@home-dev8 ~]#
1.4 退出该虚拟环境
(testenv) [root@home-dev8 ~]# deactivate
2. virtualenv执行参数简述
# virtualenv --help
optional arguments:
--version # 显示当前版本信息
--with-traceback #
--read-only-app-data #
--app-data APP_DATA #
--reset-app-data #
--upgrade-embed-wheels #
-h, --help
verbosity:
verbosity = verbose - quiet, default INFO, mapping => CRITICAL=0, ERROR=1, WARNING=2, INFO=3, DEBUG=4, NOTSET=5
-v, --verbose # 创建虚拟环境时默认会答应默认参数,通过该选项,可以打印更详细的默认参数
-q, --quiet # 静默方式,即创建虚拟环境时,不打印任何信息
discovery:
discover and provide a target interpreter
--discovery {builtin} #
-p py, --python py #
--try-first-with py_exe #
creator:
options for creator builtin
--creator {builtin,cpython2-posix} #
dest #
--clear #
--no-vcs-ignore #
--system-site-packages # 使用该参数,将同时追加操作系统原本的lib/lib64库目录,否则虚拟环境下时import不到操作系统已经安装的包
--symlinks #
--copies, --always-copy # 对于虚拟机环境需要的基本库和动态链接库(so),将拷贝置虚拟机目录,而不是默认的链接方式,这样便于将整个虚拟目录拷贝到其他节点上执行
seeder:
options for seeder app-data
--seeder {app-data,pip} #
--no-seed, --without-pip #
--no-download, --never-download #
--download #
--extra-search-dir d [d ...] #
--pip version #
--wheel version #
--setuptools version #
--no-pip # 创建虚拟目录时,不拷贝pip工具,意味着pip工具安装的新包不会存放到虚拟环境里面
--no-wheel # 创建虚拟目录时,不拷贝wheel工具,意味着wheel工具安装的新包不会存放到虚拟环境里面
--no-setuptools # 创建虚拟目录时,不拷贝setuptools工具,意味着setuptools工具安装的新包不会存放到虚拟环境里面
--no-periodic-update #
--symlink-app-data #
activators:
options for activation scripts
--activators comma_sep_list #
--prompt prompt #
3. virtualenv基本使用
3.1 创建一个虚拟环境
例如虚拟环境取名为testenv
后面输出的信息为相关参数,未指定任何参数的情况下,所有的参数都有默认值
# virtualenv testenv
created virtual environment CPython2.7.5.final.0-64 in 219ms
creator CPython2Posix(dest=/root/testenv, clear=False, no_vcs_ignore=False, global=False)
seeder FromAppData(download=False, pip=bundle, wheel=bundle, setuptools=bundle, via=copy, app_data_dir=/root/.local/share/virtualenv)
added seed packages: pip==20.3.4, setuptools==44.1.1, wheel==0.37.1
activators NushellActivator,PythonActivator,FishActivator,CShellActivator,PowerShellActivator,BashActivator
查看一下产生的testenv目录结构,展示4层目录即可
(tree命令可以通过yum install tree获得
)
# tree -d -L 4 testenv
testenv
├── bin
├── lib
│ └── python2.7
│ └── site-packages
│ ├── pip
│ ├── pip-20.3.4.dist-info
│ ├── pkg_resources
│ ├── setuptools
│ ├── setuptools-44.1.1.dist-info
│ ├── wheel
│ └── wheel-0.37.1.dist-info
└── lib64
└── python2.7
├── config
├── lib-dynload -> /usr/lib64/python2.7/lib-dynload
└── site-packages
16 directories
目录说明:
bin : python执行入口和virtualenv本身工具
lib/python2.7 : 等同于/usr/lib/python2.7筛减拷贝
lib64/python2.7 : 等同于/usr/lib64/python2.7筛减拷贝
其中testenv/bin
内的文件是需要重点关注的
# ll testenv/bin/
total 84
-rw-r--r--. 1 root root 2132 Jan 19 23:12 activate # linux 进入虚拟环境的入口
-rw-r--r--. 1 root root 1424 Jan 19 23:12 activate.csh # csh(另外一种类似bash的脚本环境)进入虚拟环境的入口
-rw-r--r--. 1 root root 3009 Jan 19 23:12 activate.fish # "the friendly interactive shell" (和csh一样,是另外一种类似bash的脚本环境)进入虚拟环境的入口
-rw-r--r--. 1 root root 1263 Jan 19 23:12 activate.nu # 一种为OS X提供的面向对象的解释型脚本
-rw-r--r--. 1 root root 1754 Jan 19 23:12 activate.ps1 # windows下PowerShell脚本
-rw-r--r--. 1 root root 1232 Jan 19 23:12 activate_this.py # 不改变原有python脚本源码的情况下,进入虚拟环境
-rw-r--r--. 1 root root 333 Jan 19 23:12 deactivate.nu # 虽然退出虚拟环境执行的是deactivate,但是不是这个脚本
-rwxr-xr-x. 1 root root 238 Jan 19 23:12 easy_install # 此为easy_install入口
-rwxr-xr-x. 1 root root 238 Jan 19 23:12 easy_install2
-rwxr-xr-x. 1 root root 238 Jan 19 23:12 easy_install-2.7
-rwxr-xr-x. 1 root root 238 Jan 19 23:12 easy_install2.7
-rwxr-xr-x. 1 root root 229 Jan 19 23:12 pip # 此为pip入口
-rwxr-xr-x. 1 root root 229 Jan 19 23:12 pip2
-rwxr-xr-x. 1 root root 229 Jan 19 23:12 pip-2.7
-rwxr-xr-x. 1 root root 229 Jan 19 23:12 pip2.7
-rwxr-xr-x. 1 root root 7216 Jan 19 23:12 python # 此为python入口
lrwxrwxrwx. 1 root root 6 Jan 19 23:12 python2 -> python
lrwxrwxrwx. 1 root root 6 Jan 19 23:12 python2.7 -> python
-rwxr-xr-x. 1 root root 216 Jan 19 23:12 wheel # 此为wheel入口
-rwxr-xr-x. 1 root root 216 Jan 19 23:12 wheel2
-rwxr-xr-x. 1 root root 216 Jan 19 23:12 wheel-2.7
-rwxr-xr-x. 1 root root 216 Jan 19 23:12 wheel
里面的python
入口实际是从当前操作系统目录中原样拷贝过来,未做任何修改
可以通过md5值确认
# md5sum testenv/bin/python
7a29324d4bc1e5fae23a08ecf19fc593 testenv/bin/python
# md5sum /usr/bin/python
7a29324d4bc1e5fae23a08ecf19fc593 /usr/bin/python
剩余的的easy_install/pip/wheel
入口实际也是从当前操作系统目录中拷贝过来,但是将入口替换为了上述虚拟目录中的python
# diff testenv/bin/pip /usr/bin/pip
1c1
< #!/root/testenv/bin/python
---
> #!/usr/bin/python
# diff testenv/bin/easy_install /usr/bin/easy_install
1c1
< #!/root/testenv/bin/python
---
> #!/usr/bin/python
# diff testenv/bin/wheel /usr/bin/wheel
1c1
< #!/root/testenv/bin/python
---
> #!/usr/bin/python
3.2 进入该虚拟环境
使用source
命令执行虚拟环境中的activate脚本即可进入环境变量,其后的shell提示符会前置追加(testenv)
提示
# source testenv/bin/activate
(testenv) [root@home-dev8 ~]#
3.3 使用该虚拟目录
举例安装python的requests库
(testenv) [root@home-dev8 ~]# pip install requests
DEPRECATION: Python 2.7 reached the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 is no longer maintained. pip 21.0 will drop support for Python 2.7 in January 2021. More details about Python 2 support in pip can be found at https://pip.pypa.io/en/latest/development/release-process/#python-2-support pip 21.0 will remove support for this functionality.
Collecting requests
Downloading requests-2.27.1-py2.py3-none-any.whl (63 kB)
|████████████████████████████████| 63 kB 330 kB/s
Collecting urllib3<1.27,>=1.21.1
Downloading urllib3-1.26.8-py2.py3-none-any.whl (138 kB)
|████████████████████████████████| 138 kB 207 kB/s
Collecting certifi>=2017.4.17
Downloading certifi-2021.10.8-py2.py3-none-any.whl (149 kB)
|████████████████████████████████| 149 kB 42 kB/s
Collecting chardet<5,>=3.0.2; python_version < "3"
Downloading chardet-4.0.0-py2.py3-none-any.whl (178 kB)
|████████████████████████████████| 178 kB 21 kB/s
Collecting idna<3,>=2.5; python_version < "3"
Downloading idna-2.10-py2.py3-none-any.whl (58 kB)
|████████████████████████████████| 58 kB 25 kB/s
Installing collected packages: urllib3, certifi, chardet, idna, requests
Successfully installed certifi-2021.10.8 chardet-4.0.0 idna-2.10 requests-2.27.1 urllib3-1.26.8
(testenv) [root@home-dev8 ~]#
查看该库的安装位置,可以看到requests被安装到了虚拟目录testenv下而非操作系统的默认的目录下
此处有意执行cd /tmp
是要强调的进入虚拟机目录后,在任意目录执行python都会有相同的效果,也就是说虚拟目录可以独立于项目目录存在
(testenv) [root@home-dev8 ~]# cd /tmp
(testenv) [root@home-dev8 tmp]#
(testenv) [root@home-dev8 tmp]# python
Python 2.7.5 (default, Oct 30 2018, 23:45:53)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-36)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import requests
>>> print requests
<module 'requests' from '/root/testenv/lib/python2.7/site-packages/requests/__init__.pyc'>
>>>
3.4 退出虚拟目录
直接执行deactivate
命令即可退出虚拟目录
(testenv) [root@home-dev8 ~]# deactivate
[root@home-dev8 ~]#
4. 更细节的virtualenv
4.1 activate进入虚拟目录实际动作
直接查看脚本内容
# 此处为一个提示,要求activate脚本必须通过source activate或. activate的方式执行
# 不可以通过类似sh activate或./activate的方式执行
# 原因是activate脚本中实际执行的各种环境变量的设置和取消
# 如果通过sh activate执行,实际是启动了一个新shell进程执行该脚本
# 环境变量的修改实际是作用在新启动的进程空间中,无法作用到当前shell中
# 而source或.实际是将activate中的内容一行行的取出后在当前shell执行
# This file must be used with "source bin/activate" *from bash*
# you cannot run it directly
# 判断如果是通过sh activate执行,则需要退出
if [ "${BASH_SOURCE-}" = "$0" ]; then
echo "You must source this script: \$ source $0" >&2
exit 33
fi
# 此处定义了一个deactivate函数,该函数即退出虚拟空间时执行deactivate命令时执行的过程
# 因此和bin/deactivate.nu没有关系
# 具体干的事情就是取消当前activate脚本设置的环境变量,并还原之前的环境变量
deactivate () {
unset -f pydoc >/dev/null 2>&1 || true
# reset old environment variables
# ! [ -z ${VAR+_} ] returns true if VAR is declared at all
if ! [ -z "${_OLD_VIRTUAL_PATH:+_}" ] ; then
PATH="$_OLD_VIRTUAL_PATH"
export PATH
unset _OLD_VIRTUAL_PATH
fi
if ! [ -z "${_OLD_VIRTUAL_PYTHONHOME+_}" ] ; then
PYTHONHOME="$_OLD_VIRTUAL_PYTHONHOME"
export PYTHONHOME
unset _OLD_VIRTUAL_PYTHONHOME
fi
# The hash command must be called to get it to forget past
# commands. Without forgetting past commands the $PATH changes
# we made may not be respected
hash -r 2>/dev/null
if ! [ -z "${_OLD_VIRTUAL_PS1+_}" ] ; then
PS1="$_OLD_VIRTUAL_PS1"
export PS1
unset _OLD_VIRTUAL_PS1
fi
unset VIRTUAL_ENV
if [ ! "${1-}" = "nondestructive" ] ; then
# Self destruct!
unset -f deactivate
fi
}
# 先做一次退出虚拟环境操作,因为有可能用户会嵌套执行activate
# unset irrelevant variables
deactivate nondestructive
# 设置VIRTUAL_ENV环境变量
VIRTUAL_ENV='/root/testenv'
if ([ "$OSTYPE" = "cygwin" ] || [ "$OSTYPE" = "msys" ]) && $(command -v cygpath &> /dev/null) ; then
VIRTUAL_ENV=$(cygpath -u "$VIRTUAL_ENV")
fi
export VIRTUAL_ENV
# PATH环境变量首位增加当前虚拟环境的bin目录,这样执行的python命令实际是虚拟环境中bin下的python
_OLD_VIRTUAL_PATH="$PATH"
PATH="$VIRTUAL_ENV/bin:$PATH"
export PATH
# unset PYTHONHOME if set
if ! [ -z "${PYTHONHOME+_}" ] ; then
_OLD_VIRTUAL_PYTHONHOME="$PYTHONHOME"
unset PYTHONHOME
fi
# 此处将shell的提示符前加入虚拟环境的目录名称
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT-}" ] ; then
_OLD_VIRTUAL_PS1="${PS1-}"
if [ "x" != x ] ; then
PS1="() ${PS1-}"
else
PS1="(`basename \"$VIRTUAL_ENV\"`) ${PS1-}"
fi
export PS1
fi
# Make sure to unalias pydoc if it's already there
alias pydoc 2>/dev/null >/dev/null && unalias pydoc || true
pydoc () {
python -m pydoc "$@"
}
# 因为修改过了环境变量,为了防止执行python还是到系统目录中找python,需要刷新hash列表
# 简单介绍下hash列表,当执行命令时Linux会到$PATH环境目录中找执行脚本,如果找到并执行后
# 会将执行命令和位置记录在hash中,这样下次再执行命令就会先看hash列表中是否存在
# 这样可以避免每次到$PATH中查找,但是带来的问题是修改$PATH环境变量后,可能会导致hash的缓存依然存在,而未更新
# 此时需要清空hash列表
# The hash command must be called to get it to forget past
# commands. Without forgetting past commands the $PATH changes
# we made may not be respected
hash -r 2>/dev/null
4.2 deactivate退出虚拟目录实际动作
了解了上述activate
脚本内容,deactivate
实际做执行的内容就迎刃而解了
就是只执行了activate
脚本中的deactivate
函数
4.3 python为何会使用虚拟目录中的库
virtualenv生效最重要的就是通过上述activate
设置了$PATH
环境变量,让执行python时实际执行的虚拟环境中的python
但是虚拟环境中的python和系统的/usr/bin/python
实际为同一个文件,为何换一个位置就可以进入虚拟空间
原因是在执行python
命令时,python会到python二进制文件所在位置的..
目录下查找../lib64/python/os.py
,
如果找到则会将python的lib目录设置到那里../lib/python
和../lib64/python
因此就不会再设置系统默认的/usr/lib/python
和/usr/lib64/python
目录,从而实现逻辑上覆盖系统默认lib库位置
未进入虚拟目录时查看环境变量
# python -m site
sys.path = [
'/root',
'/usr/lib64/python27.zip',
'/usr/lib64/python2.7',
'/usr/lib64/python2.7/plat-linux2',
'/usr/lib64/python2.7/lib-tk',
'/usr/lib64/python2.7/lib-old',
'/usr/lib64/python2.7/lib-dynload',
'/usr/lib64/python2.7/site-packages', # 注意此处
'/usr/lib/python2.7/site-packages', # 注意此处
]
USER_BASE: '/root/.local' (exists)
USER_SITE: '/root/.local/lib/python2.7/site-packages' (doesn't exist)
ENABLE_USER_SITE: True
进入虚拟目录时查看环境变量
[root@test-centos-9 ~]# source testenv/bin/activate
(testenv) [root@test-centos-9 ~]# python -m site
sys.path = [
'/root',
'/usr/lib64/python27.zip',
'/usr/lib64/python2.7',
'/usr/lib64/python2.7/plat-linux2',
'/usr/lib64/python2.7/lib-tk',
'/usr/lib64/python2.7/lib-old',
'/usr/lib64/python2.7/lib-dynload',
'/root/testenv/lib64/python2.7/site-packages', # 注意此处
'/root/testenv/lib/python2.7/site-packages', # 注意此处
]
USER_BASE: '/root/.local' (exists)
USER_SITE: '/root/.local/lib/python2.7/site-packages' (doesn't exist)
ENABLE_USER_SITE: False
退出虚拟目录,直接使用虚拟目录中的Python时查看环境变量
(testenv) [root@test-centos-9 ~]# deactivate
[root@test-centos-9 ~]# ./testenv/bin/python
Python 2.7.5 (default, Oct 30 2018, 23:45:53)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-36)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> exit()
[root@test-centos-9 ~]# ./testenv/bin/python -m site
sys.path = [
'/root',
'/usr/lib64/python27.zip',
'/usr/lib64/python2.7',
'/usr/lib64/python2.7/plat-linux2',
'/usr/lib64/python2.7/lib-tk',
'/usr/lib64/python2.7/lib-old',
'/usr/lib64/python2.7/lib-dynload',
'/root/testenv/lib64/python2.7/site-packages', # 注意此处
'/root/testenv/lib/python2.7/site-packages', # 注意此处
]
USER_BASE: '/root/.local' (exists)
USER_SITE: '/root/.local/lib/python2.7/site-packages' (doesn't exist)
ENABLE_USER_SITE: False
4.4 不使用virtualenv构建一个独立的python运行环境
了解了虚拟机目录的本质,就可以不安装virtualenv自己构建虚拟环境,并且可以将该环境拷贝到其他机器直接执行
# mkdir testenv2
# cd testenv2/
# mkdir -p {bin,lib,lib64}
# cp /usr/bin/python bin
# cp -r /usr/lib/python2.7 lib -r
# cp -r /usr/lib64/python2.7 lib64
# ./bin/python -m site
sys.path = [
'/root/testenv2',
'/usr/lib64/python27.zip',
'/usr/lib64/python2.7',
'/usr/lib64/python2.7/plat-linux2',
'/usr/lib64/python2.7/lib-tk',
'/usr/lib64/python2.7/lib-old',
'/usr/lib64/python2.7/lib-dynload',
'/usr/lib64/python2.7/site-packages',
'/usr/lib/python2.7/site-packages',
]
USER_BASE: '/root/.local' (exists)
USER_SITE: '/root/.local/lib/python2.7/site-packages' (doesn't exist)
ENABLE_USER_SITE: True
总结:
virtualenv本质做了上述4.4章节描述的事情,不过它做的会更高效,比如并非将源lib/lib64整个目录拷贝过来
同时使用它的"system-site-packages"参数实现python同时访问虚拟目录的lib/lib64目录,也可以访问操作系统原来的lib/lib64目录等