@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閱讀 227,837評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,196評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,688評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,654評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,456評論 6 406
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,955評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,044評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,195評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,725評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,608評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,802評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,318評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,048評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,422評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,673評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,424評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,762評論 2 372

推薦閱讀更多精彩內容