概述
背景
- 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
。每當在系統中執行一個命令時,例如python
或pip
,操作系統就會在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
可能遇到的問題
筆者在使用 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.1
,PROJECT_NAME
是我們自定義的項目名稱。比較好的實踐方式是,在PROJECT_NAME
也帶上Python的版本號,以便于識別。
現假設我們有test-pyenv
這么一個項目,想針對Python 2.7.8
和Python 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 XXX
或pyenv 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命令,基本可以滿足絕大多數依賴庫環境管理方面的需求。