背景
从接触Python以来,一直都是采用virtualenv
和virtualenvwrapper
来管理不同项目的依赖环境,通过workon
、mkvirtualenv
等命令进行虚拟环境切换,很是愉快。
然而,最近想让项目能兼容更多的Python版本,例如至少同时兼容Python2.7
和Python3.3+
,就发现采用之前的方式行不通了。
最大的问题在于,在本地计算机同时安装Python2.7
和Python3
后,即使分别针对两个Python版本安装了virtualenv
和virtualenvwrapper
,也无法让两个Python版本的workon
、mkvirtualenv
命令同时生效。另外一方面,要想在本地计算机安装多个Python版本,会发现安装的成本都比较高,实现方式也不够优雅。
幸运地是,针对该痛点,已经存在一个比较成熟的方案,那就是pyenv
。
如下是官方的介绍。
pyenv lets you easily switch between multiple versions of Python. It’s simple, unobtrusive, and follows the UNIX tradition of single-purpose tools that do one thing well.
This project was forked from rbenv and ruby-build, and modified for Python.
本文就针对pyenv
最核心的功能进行介绍。
基本原理
如果要讲解pyenv
的工作原理,基本上采用一句话就可以概括,那就是:修改系统环境变量PATH
。
对于系统环境变量PATH
,相信大家都不陌生,里面包含了一串由冒号分隔的路径,例如/usr/local/bin:/usr/bin:/bin
。每当在系统中执行一个命令时,例如python
或pip
,操作系统就会在PATH
的所有路径中从左至右依次寻找对应的命令。因为是依次寻找,因此排在左边的路径具有更高的优先级。
而pyenv
做的,就是在PATH
最前面插入一个$(pyenv root)/shims
目录。这样,pyenv
就可以通过控制shims
目录中的Python版本号,来灵活地切换至我们所需的Python版本。
如果还想了解更多细节,可以查看pyenv
的文档介绍及其源码实现。
环境初始化
pyenv
的安装方式包括多种,重点推荐采用pyenv-installer
的方式,原因主要有两点:
- 通过
pyenv-installer
可一键安装pyenv
全家桶,后续也可以很方便地实现一键升级; pyenv-installer
的安装方式基于GitHub
,可保证总是使用到最新版本的pyenv
,并且Python
版本库也是最新最全的。
install && config
通过如下命令安装pyenv
全家桶。
1 | $ curl -L https://raw.githubusercontent.com/pyenv/pyenv-installer/master/bin/pyenv-installer | bash |
内容除了包含pyenv
以外,还包含如下插件:
pyenv-doctor
pyenv-installer
pyenv-update
pyenv-virtualenv
pyenv-which-ext
安装完成后,pyenv
命令还没有加进系统的环境变量,需要将如下内容加到~/.zshrc
中,然后执行source ~/.zshrc
。
1 | export PATH=$HOME/.pyenv/bin:$PATH |
完成以上操作后,pyenv
就安装完成了。
1 | $ pyenv -v |
如果不确定pyenv
的环境是否安装正常,可以通过pyenv doctor
命令对环境进行检测。
1 | $ pyenv doctor |
通过检测,可以发现本地环境可能存在的问题,例如,从以上输出可以看出,本地的OpenSSL development header
还没有安装。根据提示的问题,逐一进行修复,直到检测不再出现问题为止。
update
通过pyenv update
命令,可以更新pyenv
全家桶的所有内容。
1 | $ pyenv update |
pyenv的核心使用方法
pyenv
的主要功能如下:
1 | $ pyenv -h |
查看所有可安装的Python
版本
1 | $ pyenv install --list |
需要注意的是,如果是采用brew
命令安装的pyenv
,可能会发现Python
版本库中没有最新的Python
版本。所以建议还是通过GitHub
源码方式安装pyenv
。
安装指定版本的Python
环境
1 | $ pyenv install 3.6.0 |
查看当前系统中所有可用的Python
版本
1 | $ pyenv versions |
切换Python
版本
pyenv
可以从三个维度来管理Python
环境,简称为:当前系统
、当前目录
、当前shell
。这三个维度的优先级从左到右依次升高,即当前系统
的优先级最低、当前shell
的优先级最高。
如果想修改系统全局的Python环境,可以采用pyenv global PYTHON_VERSION
命令。该命令执行后会在$(pyenv root)
目录(默认为~/.pyenv
)中创建一个名为version
的文件(如果该文件已存在,则修改该文件的内容),里面记录着系统全局的Python版本号。
1 | $ pyenv global 2.7.13 |
通常情况下,对于特定的项目,我们可能需要切换不同的Python环境,这个时候就可以通过pyenv local PYTHON_VERSION
命令来修改当前目录
的Python环境。命令执行后,会在当前目录中生成一个.python-version
文件(如果该文件已存在,则修改该文件的内容),里面记录着当前目录使用的Python版本号。
1 | $ cat ~/.pyenv/version |
可以看出,当前目录中的.python-version
配置优先于系统全局的~/.pyenv/version
配置。
另外一种情况,通过执行pyenv shell PYTHON_VERSION
命令,可以修改当前shell
的Python环境。执行该命令后,会在当前shell session
(Terminal窗口)中创建一个名为PYENV_VERSION
的环境变量,然后在当前shell
的任意目录中都会采用该环境变量设定的Python版本。此时,当前系统
和当前目录
中设定的Python版本均会被忽略。
1 | $ echo $PYENV_VERSION |
顾名思义,当前shell
的Python环境仅在当前shell中生效,重新打开一个新的shell后,该环境也就失效了。如果想在当前shell
中取消shell级别的Python环境,采用unset
命令重置PYENV_VERSION
环境变量即可。
1 | $ cat .python-version |
管理多个依赖库环境
经过以上操作,我们在本地计算机中就可以安装多个版本的Python
运行环境,并可以按照实际需求进行灵活地切换。然而,很多时候在同一个Python
版本下,我们仍然希望能根据项目进行环境分离,就跟之前我们使用virtualenv
一样。
在pyenv
中,也包含这么一个插件,pyenv-virtualenv
,可以实现同样的功能。
使用方式如下:
1 | $ pyenv virtualenv PYTHON_VERSION PROJECT_NAME |
其中,PYTHON_VERSION
是具体的Python版本号,例如,3.6.0
,PROJECT_NAME
是我们自定义的项目名称。比较好的实践方式是,在PROJECT_NAME
也带上Python的版本号,以便于识别。
现假设我们有XDiff
这么一个项目,想针对Python 2.7.13
和Python 3.6.0
分别创建一个虚拟环境,那就可以依次执行如下命令。
1 | $ pyenv virtualenv 3.6.0 py36_XDiff |
创建完成后,通过执行pyenv virtualenvs
命令,就可以看到本地所有的项目环境。
1 | $ pyenv virtualenvs |
通过这种方式,在同一个Python版本下我们也可以创建多个虚拟环境,然后在各个虚拟环境中分别维护依赖库环境。
例如,py36_XDiff
虚拟环境位于/Users/Leo/.pyenv/versions/3.6.0/envs
目录下,而其依赖库位于/Users/Leo/.pyenv/versions/3.6.0/lib/python3.6/site-packages
中。
1 | $ pip -V |
后续在项目开发过程中,我们就可以通过pyenv local XXX
或pyenv activate PROJECT_NAME
命令来切换项目的Python
环境。
1 | ➜ MyProjects pyenv local py27_XDiff |
可以看出,切换环境后,pip
命令对应的目录也随之改变,即始终对应着当前的Python虚拟环境。
对应的,采用pyenv deactivate
命令退出当前项目的Python
虚拟环境。
如果想移除某个项目环境,可以通过如下命令实现。
1 | $ pyenv uninstall PROJECT_NAME |
以上便是日常开发工作中常用的pyenv
命令,基本可以满足绝大多数依赖库环境管理方面的需求。