个人认为的Python虚拟环境的最佳实践。

Python开发最大的一个问题就是版本兼容和环境隔离。Python的版本众多,还有数量庞大的第三方库,每个库可能还有众多版本,这时候如何确保在移植项目的时候正确安装python和第三方库就十分重要了。比如,项目A和B需要不同的python版本,A只能运行在python2.7上,而B只能运行在python3.6上。又或者,A项目需要tensorflow 1.15,B项目则构建在tensorflow 2.1上。

针对这个问题,个人认为最好用的就是pyenv+virtualenv+pip组合:

  1. pyenv用来管理不同版本的Python

  2. virtualenv可以根据pyenv提供Python,来创建独立的Python虚拟环境

  3. 进入virtualenv创建的虚拟环境之后,使用pip安装需要的官方或第三方包,一般定义在requirements.txt

pyenv

pyenv可以安装并管理不同的python版本。

Installation

首先安装依赖库文件[https://github.com/pyenv/pyenv/wiki#suggested-build-environment]

sudo apt-get update 
sudo apt-get install make build-essential libssl-dev zlib1g-dev \ 
libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm libncursesw5-dev \ 
xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev

这里使用pyenv自带的installer安装[https://github.com/pyenv/pyenv-installer]

将pyenv安装到推荐位置~/.pyenv,重启shell可以使安装生效。

curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash

# activate ~/.bashrc
source ~/.bashrc

Configuration

添加下列配置信息到~/.bashrc,重启shell使安装生效。

export PATH="$HOME/.pyenv/bin:$PATH"
eval "$(pyenv init --path)"
eval "$(pyenv virtualenv-init -)"

# activate ~/.bashrc
source ~/.bashrc

Update

pyenv也需要定期更新才能显示最新的可以安装的python版本,通过下面的命令进行更新

pyenv update

Uninstallation

删除~/.pyenv文件夹

rm -rf ~/.pyenv

~/.bashrc中删除下列配置信息

export PATH="$HOME/.pyenv/bin:$PATH"
eval "$(pyenv init --path)"
eval "$(pyenv virtualenv-init -)"

# activate ~/.bashrc
source ~/.bashrc

Manage Python using pyenv

下面的命令可以显示所有可以安装的python版本

pyenv install --list

安装特定的python版本

pyenv install <PYTHON-VERSION>
# e.g., pyenv install 3.7.12

检查安装python版本,正常应该显示,system3.7.12

# 注意version和versions是不同命令
pyenv versions

可以通过下面命令卸载任何已经安装的python

pyenv uninstall <PYTHON-VERSION>

pyenv提供了global和local两个设定python的方式。

# global level
pyenv global <python-version>

# local level
pyenv local <python-version>

但是不推荐global的方式,毕竟可能涉及到系统python的设置。对于想用pyenv来管理不同的python版本,并且在创建virtualenv的时候指定对应的版本,那这里不用通过pyenv来设置global或者local,让pyenv保持system既可。

virtualenv

virtualenv应该是目前使用最广的python创建python虚拟环境的方式,它的特点在于安装使用简单,可以快速创建一个独立干净的python环境(无多与的pip安装包)。virtualenv对于创建不同环境用来部署不同版本的tensorflow和pytorch尤其有用。但是这个方法有一个问题,它创建的虚拟环境需要基于系统已经存在的python(如果安装了多个python版本,那么可以指定这些版本创建环境)。比如,如果系统安装了python3.8,那么创建的所有虚拟环境的解释器都是python3.8,这就意味着virtualenv无法创建一个基于python3.5的虚拟环境。

Installation

virtualenv是python主要的虚拟环境包。安装方式很简单

sudo apt install virtualenv
# or 
# sudo pip3 install virtualenv

没有root权限进行pip3 install virtualenv,会无法使用virtualenv命令,因为带root权限会安装virtualenv到/usr/lib/pythonX.X/dist-packages,而如果不带root权限会安装到~/.local/lib/pythonX.X/site-packages

Create virtualenv

安装后,就可以直接使用virtualenv命令创建一个独立的干净Python运行环境。

virtualenv也可以是显式指定创建的虚拟环境使用哪个python版本(制定的版本必须已经预先安装,如果没有安装对应的python版本,virtualenv则无法创建)。

virtualenv <venv-name>
virtualenv -p python3 <venv-name>
virtualenv --python=/usr/bin/python3 <venv-name>

其实,上面三个命令效果相同,其实从后面两个命令可以看出,virtualenv需要找到python解释器,然后才能创建基于这个版本解释器的python虚拟环境。

Activate and deactivate virtualenv

默认使用的是用户和系统环境,激活后才是虚拟环境。

source venv/bin/activate

这时命令行前面会出现(<venv-name>),就证明进入了虚拟环境。使用pip3 list会发现此环境只有初始化时候添加的pip, setuptools, wheel

通过deactivate就可以退出虚拟环境

deactivate

pyenv + virtualenv + pip

配合pyenv,我们就可以使用不同的python版本创建虚拟环境。

# 指定python的路径为pyenv安装的位置,比如:
virtualenv --python=/home/{user_name}/.pyenv/versions/3.7.9/bin/python3 <venv-name>

可以通过pip3 install -U setuptoolspip3 install -U pip来更新创建的虚拟环境中的pipsetuptools

在此虚拟环境中,可以通过pip来安装需要的包。

同时,我们可以通过源码编译安装某一Python版本,并指定virtualenv使用对应的地址创建虚拟环境

Misc

venv

从Python 3.3开始,标准库中就自带了一个venv模块,拥有virtualenv的部分功能。因此,也可以通过以下命令来创建虚拟环境。

python3 -m venv venv-name

当然了因为只有部分功能,所以还是推荐使用virtualenv。比如,venv只能根据当前python解释器来创建虚拟环境。

pipenv

pipenv是 Python 官方推荐的包管理工具,它综合了virtualenv,pyenv,pip三者的功能,它的目标是将所有包管理领域[bundler, composer, npm, cargo, yarn 等]的最好体验带到python世界中来 (Pipenv is a tool that aims to bring the best of all packaging worlds (bundler, composer, npm, cargo, yarn, etc.) to the Python world.)。

pipenv可以通过简单的命令创建一个python虚拟环境,这是并不需要像virtualenv一样[当然也可以做到],通过source命令进入虚拟环境,而是可以通过pipenv run ...命令在对应的虚拟环境中执行操作,比如pipenv run pip3 install torch torchvision torchaudio会在创建的虚拟环境中安装 pytorch。pipenv的更多命令和具体使用说明可以在其官网找到,不再赘述。简单来说,就是用pipenv一个命令实现pyenv+virtualenv+pip的功能。

requirements.txt vs pipfile

其实在一些场景下requirements方式就可以满足需求,但是在复杂场景下这种方式就力不从心了:

  1. requirements.txt文件中只记录了依赖的版本,所以如果遇到官方的 pypi 源下载速度慢,需要使用其他的镜像下载,通常只能使用pip install -i安装或者修改全局的pip.conf文件。这对于自动部署都是不友好的。

  2. 当某个项目使用确定的python版本,python版本不能在requirements.txt中体现,只能通过 readme 或者文档来记录,并且需要在创建虚拟环境时手动使用正确的python版本。

  3. 开发环境和生产环境需要的依赖库可能是不一样,但是requirements.txt无法体现出这些出别,例如,项目需要使用flake8,pylint,black等代码优化工具时,这些依赖也会被pip freeze命令写入requirements.txt中,然而这些依赖是不需要出现在生产环境的。

pipenvpipfile的出现可以解决上出问题,pipfile允许设置pip源,并且支持默认环境和开发环境不同的依赖。

例如下面的例子pipfile[[source]]定义了pip源,[dev-packages]定义了开发环境下的依赖,[packages]定义了默认环境下的依赖,[requires]则确定了python的具体版本。

[[source]]
name = "pypi"
url = "https://xxx.xxxxx.xxx/simple"
verify_ssl = false

[dev-packages]
black = "==19.3b0"
pylint = "*"

[packages]
flask = "*"
pandas = "*"
requests = "*"
openpyxl = "*"

[requires]
python_version = "3.6"

同时,pipenv可以自动生成Pipfile.lock文件,中记录了当前环境中安装的依赖的版本号以及哈希,以保证每次根据这些值安装的依赖都是一致的,该文件用来保证包的完整性。任何情况下不要手动修改该文件