@python多版本管理及虚拟环境策略:homebrew + pyenv + pyenv-virtualenv(macOS Majave)

概述

背景

  • Python 解释器版本混乱, 2和3差别巨大, 而且细分版本也不尽相同, 难以选择和管理。
  • 不同 Linux 发行版自带 Python 不同, 如 macOSX 自带 2.7 版本, 其中系统许多组件依赖于自带解释器, 一旦删除或者更改都可能会造成系统出问题。
  • 不同的 Python 解释器软件包管理也是问题, 如 pip 和 ipython 等必备包组件, 而且在项目开发中如何保证不同的包环境互不干扰也是一个问题。

那么有没有一个终极的解决办法能在管理不同解释器版本的同时控制不同的包环境呢? 有的, 就是 pyenv.

pyenv 是什么? 能干什么?

pyenv 是一个 forked 自 ruby 社区的简单、低调、遵循 UNIX 哲学的Python 环境管理工具, 它可以轻松切换全局解释器版本, 同时结合 vitualenv 插件可以方便的管理对应的包源。

使用 pyenv 我可以方便的下载指定版本的 python 解释器, pypy, anaconda 等, 可以随时自由的在 “shell环境、本地、全局”切换python解释器。

开发的时候不需要限定某个版本的虚拟环境, 只需要在部署的时候用 pyenv 指定某个版本就好了。

pyenv 切换解释器版本的时候, pip 和 ipython 以及对应的包环境都是一起切换的, 所以如果你要同时运行 ipython2.x 和 ipython3.x 多个解释器验证一些代码时就很方便。

pyenv 也可以创建好指定的虚拟环境, 但不需要指定具体目录, 自由度更高, 使用也简单。

基本原理

如果要讲解pyenv的工作原理,基本上采用一句话就可以概括,那就是:修改系统环境变量PATH

对于系统环境变量PATH,相信大家都不陌生,里面包含了一串由冒号分隔的路径,例如/usr/local/bin:/usr/bin:/bin。每当在系统中执行一个命令时,例如pythonpip,操作系统就会在PATH的所有路径中从左至右依次寻找对应的命令。因为是依次寻找,因此排在左边的路径具有更高的优先级。

pyenv做的,就是在PATH最前面插入一个$(pyenv root)/shims目录。这样,pyenv就可以通过控制shims目录中的Python版本号,来灵活地切换至我们所需的Python版本。

如何安装?

安装 pyenv

本文只介绍 mac 下利用 homebrew 的安装过程,其它系统安装过程大同小异,具体可参考官方的安装手册

step0: preinstall

#确保本机已安装 xcode 且为最新版
[mac]xcode-select --install
#确保本机已安装相关依赖
[mac]brew install zlib openssl readline xz sqlite

step1: 安装 pyenv

[mac] brew install pyenv

step2: 在 ~/.zshrc 添加以下内容

#pyenv
export PYENV_ROOT=$(brew --prefix pyenv)
export PATH="$PYENV_ROOT/bin:$PATH"
if command -v pyenv 1>/dev/null 2>&1; then
  eval "$(pyenv init -)"
fi

step3: 使 .zshrc 生效,并重新启动 shell

[mac] source ~/.zshrc
[mac] exec $SHELL       

step4:验证是否安装成功

[mac]pyenv -v
pyenv 1.2.8

可能遇到的问题

若遇到无法安装的问题可参考 此文1 此文2,即可解决。

笔者在使用 brew install pyenv 后遇到以下问题:

  • 问题1:
    笔者在安装 pyenv 之前,mac 中已装有以下 python:
  • 系统自带的 python 2.7
  • homebrew 安装的 python@2 和 python3(python 3.7.1)
[mac]pyenv install -l
Available versions:
/usr/local/bin/python-build: /usr/local/bin/sort: /usr/local/opt/python3/bin/python3.6: bad interpreter: No such file or directory

于是,笔者便分别查看了文件/usr/local/bin/python-build和文件/usr/local/bin/sort,发现 /usr/local/opt/python3/bin/ 中并没有 python3.6,只有 python3.7,于是手动 ln -s [python3.7的 bin 路径] /usr/local/opt/python3/bin/python3.6,修改后发现又出现了问题2。

  • 问题2
[mac]pyenv install -l
Available versions:
Traceback (most recent call last):
  File "/usr/local/bin/sort", line 7, in <module>
    from sort import cli
ModuleNotFoundError: No module named 'sort'

于是,vi /usr/local/bin/sort:

def cli():
    print('This is suroegin's package - sort')

没发现有什么问题,试着将print('This is suroegin's package - sort') -> print("This is suroegin's package - sort"),又出现了问题3。

  • 问题3
    具体的报错信息忘记了,大致包含以下关键词:
[mac]pyenv install -l
unmatched '

最后

[mac]pip3 uninstall sort

所有问题就解决了。

总结:因为笔者之前有安装 python,导致安装 pyenv 后,pyenv 的相关依赖使用了笔者系统中已安装的模块,以致出现相关依赖问题。所以,一个好的版本管理策略是多么重要,仅仅依赖 homebrew 的 brew switch python 3.x.x 并不能从根本上解决 python 的版本管理问题。

安装 pyenv-virtualenv

step1: install

[mac]brew install pyenv-virtualenv

step2: 修改 ~/.zshrc为以下:

#pyenv
export PYENV_ROOT=$(brew --prefix pyenv)
export PATH="$PYENV_ROOT/bin:$PATH"
if which pyenv 1>/dev/null 2>&1; then
  eval "$(pyenv init -)"
fi
if which pyenv-virtualenv-init > /dev/null; then
  eval "$(pyenv virtualenv-init -)"
fi

step3: 使 .zshrc 生效

[mac]$ source ~/.zshrc

如何使用

pyenv 常用命令

pyenv 的主要功能如下:

$ pyenv -h
Usage: pyenv <command> [<args>]

Some useful pyenv commands are:
   commands    List all available pyenv commands
   local       Set or show the local application-specific Python version
   global      Set or show the global Python version
   shell       Set or show the shell-specific Python version
   install     Install a Python version using python-build
   uninstall   Uninstall a specific Python version
   rehash      Rehash pyenv shims (run this after installing executables)
   version     Show the current Python version and its origin
   versions    List all Python versions available to pyenv
   which       Display the full path to an executable
   whence      List all Python versions that contain the given executable

See `pyenv help <command>' for information on a specific command.
For full documentation, see: https://github.com/pyenv/pyenv#readme

比如:

# 查看当前激活的是那个版本的Python
pyenv version

# 查看所有已安装的版本
pyenv versions

# 查看所有可安装的版本
pyenv install --list

# 安装指定版本
pyenv install 3.6.5
# 安装完成后必须rehash
pyenv rehash

# 删除指定版本
pyenv uninstall 3.5.2

# 指定局部版本,当前目录生效
pyenv local 3.6.5

# 指定全局版本,整个系统生效
pyenv global 3.6.5

# 指定多个全局版本, 3版本优先
pyenv global 3.6.5 2.7.14

# 取消设置
pyenv local --unset

# 实际上当你切换版本后, 相应的pip和包仓库都是会自动切换过去的

使用 pyenv:切换 python 版本

pyenv 可以从三个维度来管理Python环境,简称为:当前系统(global)当前目录(local)当前shell。这三个维度的优先级从左到右依次升高,即当前系统的优先级最低、当前shell的优先级最高。

如果想修改系统全局的Python环境,可以采用 pyenv global PYTHON_VERSION 命令。该命令执行后会在 $(pyenv root) 目录中创建一个名为 version 的文件(如果该文件已存在,则修改该文件的内容),里面记录着系统全局的Python版本号。

[mac]$ pyenv global system
[mac]$ cat $(pyenv root)/version
system
[mac]$ pyenv version
system (set by /usr/local/opt/pyenv/version)

通常情况下,对于特定的项目,我们可能需要切换不同的Python环境,这个时候就可以通过pyenv local PYTHON_VERSION 命令来修改当前目录的Python环境。命令执行后,会在当前目录中生成一个.python-version文件(如果该文件已存在,则修改该文件的内容),里面记录着当前目录使用的Python版本号。

[mac]$ cd ~/workspace/test-pyenv
[mac]pyenv local 2.7.8
[mac]$ cat .python-version
2.7.8
[mac]$ pyenv version
2.7.8 (set by /Users/seyvoue/workspace/test-pyenv/.python-version)
[mac]$ pip -V
pip 18.1 from /usr/local/opt/pyenv/versions/2.7.8/lib/python2.7/site-packages/pip (python 2.7)

可以看出,当前目录中的.python-version配置优先于系统全局的$(pyenv root)/version配置。

另外一种情况,通过执行 pyenv shell PYTHON_VERSION 命令,可以修改当前shell的Python环境。执行该命令后,会在当前shell session(Terminal窗口)中创建一个名为PYENV_VERSION 的环境变量,然后在当前shell的任意目录中都会采用该环境变量设定的Python版本。此时,当前系统当前目录中设定的Python版本均会被忽略。

[mac]$ cd ~/workspace/test-pyenv
[mac]pyenv local 2.7.8
[mac]$ cat .python-version
2.7.8
[mac]$ pyenv version
2.7.8 (set by /Users/seyvoue/workspace/test-pyenv/.python-version)
[mac]$ echo $PYENV_VERSION

[mac]$ pyenv shell 3.7.1
[mac]$ echo $PYENV_VERSION
3.7.1
[mac]$ cat .python-version
2.7.8
[mac]$ pyenv version
3.7.1 (set by PYENV_VERSION environment variable)

顾名思义,当前shell的Python环境仅在当前shell中生效,重新打开一个新的shell后,该环境也就失效了。如果想在当前shell中取消shell级别的Python环境,采用unset命令重置PYENV_VERSION环境变量即可。

cat .python-version
2.7.8
[mac]$ pyenv version
3.7.1 (set by PYENV_VERSION environment variable)
[mac]$ unset PYENV_VERSION
2.7.8 (set by /Users/seyvoue/workspace/test-pyenv/.python-version)

特别建议:

系统全局用系统默认的Python比较好,不建议直接对其操作
pyenv global system
用local进行指定版本切换,一般开发环境使用。
pyenv local 2.7.10
对当前用户的临时设定Python版本,退出后失效
pyenv shell 3.5.0
取消某版本切换
pyenv local 3.5.0 --unset

输入python即可使用新版本的python
系统自带的脚本会以 /usr/bin/python 的方式直接调用老版本的python,因而不会对系统脚本产生影响;
如果通过homebrew安装python,那么pip会同时安装。

使用 pyenv-virtualenv:管理多个依赖库环境

经过以上操作,我们在本地计算机中就可以安装多个版本的Python运行环境,并可以按照实际需求进行灵活地切换。然而,很多时候在同一个Python版本下,我们仍然希望能根据项目进行环境分离,在pyenv中,pyenv-virtualenv 插件可以实现这个功能。

使用方式如下:

$ pyenv virtualenv PYTHON_VERSION PROJECT_NAME

其中,PYTHON_VERSION是具体的Python版本号,例如,3.7.1PROJECT_NAME是我们自定义的项目名称。比较好的实践方式是,在PROJECT_NAME也带上Python的版本号,以便于识别。

现假设我们有test-pyenv这么一个项目,想针对Python 2.7.8Python 3.7.1分别创建一个虚拟环境,那就可以依次执行如下命令。

$ pyenv virtualenv 3.7.1 py37_test-pyenv
$ pyenv virtualenv 2.7.8 py27_test-pyenv

创建完成后,通过执行pyenv virtualenvs命令,就可以看到本地所有的项目环境。

$ pyenv virtualenvs 
2.7.8/envs/py27_test-pyenv (created from /usr/local/opt/pyenv/versions/2.7.8)
3.7.1/envs/py37_test-pyenv (created from /usr/local/opt/pyenv/versions/3.7.1)
py27_test-pyenv (created from /usr/local/opt/pyenv/versions/2.7.8)
py37_test-pyenv (created from /usr/local/opt/pyenv/versions/3.7.1)

通过这种方式,在同一个Python版本下我们也可以创建多个虚拟环境,然后在各个虚拟环境中分别维护依赖库环境。

例如,py37_test-pyenv虚拟环境位于$(pyenv root)/versions/3.7.1/envs目录下,而其依赖库位于$(pyenv root)/versions/3.7.1/lib/python3.7/site-packages中。

$ cd ~/workspace/test-pyenv
$ pyenv version
2.7.8 (set by /Users/seyvoue/workspace/test-pyenv/.python-version)
$ pip -V
pip 18.1 from /usr/local/opt/pyenv/versions/2.7.8/lib/python2.7/site-packages/pip (python 2.7)

后续在项目开发过程中,我们就可以通过pyenv local XXXpyenv activate PROJECT_NAME命令来切换项目的Python环境。

$ cd ~/workspace/test-pyenv
$ pyenv local py37_test-pyenv
$ pyenv version
py37_test-pyenv (set by /Users/seyvoue/workspace/test-pyenv/.python-version)
$ python -V
Python 3.7.1
$ pip -V
pip 10.0.1 from /usr/local/opt/pyenv/versions/3.7.1/envs/py37_test-pyenv/lib/python3.7/site-packages/pip (python 3.7)

可以看出,切换环境后,pip命令对应的目录也随之改变,即始终对应着当前的Python虚拟环境。

对应的,采用pyenv deactivate命令退出当前项目的Python虚拟环境。

如果想移除某个项目环境,可以通过如下命令实现。

$ pyenv uninstall PROJECT_NAME

以上便是日常开发工作中常用的pyenv命令,基本可以满足绝大多数依赖库环境管理方面的需求。

参考链接

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容