iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 4
0
Software Development

用舒服的姿勢開發 Python Project系列 第 4

[用舒服的姿勢開發 Python Project] Day 04 - Pyenv 的其他使用方法及原理

Pyenv 其他使用方法

除了上述安裝、解除安裝、在不同 Scope 切換不同的 Python 版本以外,以下還有一些比較特別的使用方法

使用 pyenv shell

在 shell 配置檔末加入 pyenv init - 將可以自動載入 pyenv,接著才能夠使用 pyenv shell

pyenv shell <verison> 將可以設定 PYENV_VERSION 環境變數,作為 shell 使用的 Python 版本,此版本將會覆蓋全域及區域的 Python 版本,若不需要可以使用 pyenv shell --unset 取消。

當想要在 shell 中使用多個版本的 Python 的話,可以輸入 pyenv shell <version>...,輸入後便可以在環境中使用 Python 的版本,舉例來說,想要可以使用 2.7.7 和 3.8.0 的話可以輸入 pyenv shell 2.7.6 3.8.0,接著可以看到下面的結果:

$ pyenv versions
  system
* 2.7.6 (set by PYENV_VERSION environment variable)
* 3.3.3 (set by PYENV_VERSION environment variable)
$ python --version
Python 2.7.6
$ python2.7 --version
Python 2.7.6
$ python3.3 --version
Python 3.3.3

輸入比較前面的版號將會為是優先使用的 Python 版本,所以指令 python 會使用 2.7.6

另外,pyenv globalpyenv local 同樣接受多個版本號的參數,作用如同 pyenv shell 輸入多個版本號,差別在於 pyenv shell 版本將會覆蓋 pyenv local 版本,並且 pyenv local 版本會覆蓋 pyenv global 版本。

使用 pyenv rehash

pyenv rehash 是在每次下載新的 Pyton 版本時,用於更新 Shims 可以使用的指令,其實在 pyenv initpyenv install 中都會在執行這個指令,供使用者方便使用。

淺析 Pyenv 原理

如同前面介紹 pyenv init 時提及的, pyenv 將會修改 PATH 這個環境變數,要了解 Pyenv 的運作怎麼切換不同版本的 Python 首先要先了解 PATH 環境變數,以及 Shims 在 Pyenv 中扮演的角色為何。

PATH 環境變數

當想要在 shell 中執行任何指令時,系統首先要知道這些指令是什麼,然而系統便會去一個個的路徑尋找相同名字的可執行檔案,而這些路徑將會首先定義在 PATH 環境變數中,若在 shell 中執行 echo $PATH 將可以看到一串由冒號分隔的字串,例如:

/Users/xxx/.pyenv/shims:/usr/local/opt/llvm/bin:/Library/Frameworks/Python.framework/Versions/3.5/bin:/opt/local/bin/:/Users/xxx/bin

系統將會由左至右開始查找,因此在前面的目錄先找到的話便不會往下繼續找,而當輸入 eval "$(pyenv init -)" 時會將把 ${PYENV_ROOT}/shims 加入 PATH 的最前面,因此達到呼叫 Pyenv shims 中的指令而非系統的。

Shim 是什麼

Shim 在維基百科的解釋是:

In computer programming, a shim is a library that transparently intercepts API calls and changes the arguments passed, handles the operation itself or redirects the operation elsewhere.

大意指的是 Shim 的主要工作就是擷取 API 呼叫並且改變其中的參數,隨後將改變後的參數傳給其他執行單元執行、或自身處理。
而在 ${PYENV_ROOT}/shims中的每支腳本都是做這樣的事情(Pyenv 稱之為 rehashing),其中的程式碼如下:

#!/usr/bin/env bash
set -e
[ -n "$PYENV_DEBUG" ] && set -x

program="${0##*/}"
if [[ "$program" = "python"* ]]; then
  for arg; do
    case "$arg" in
    -c* | -- ) break ;;
    */* )
      if [ -f "$arg" ]; then
        export PYENV_FILE_ARG="$arg"
        break
      fi
      ;;
    esac
  done
fi

export PYENV_ROOT="/Users/wilson/.pyenv"
exec "/usr/local/Cellar/pyenv/1.2.20/libexec/pyenv" exec "$program" "$@"

其中可以看到最後一行會將輸入的指令及參數帶入至 pyenv exec 執行,這些程式碼也是在 pyenv rehash 時建立於 ${PYENV_ROOT}/shims 下的。

pyenv exec 在做什麼

查看原始碼可以發現,下面者一段:

PYENV_COMMAND_PATH="$(pyenv-which "$PYENV_COMMAND")"
PYENV_BIN_PATH="${PYENV_COMMAND_PATH%/*}"
# ...
if [ "${PYENV_BIN_PATH#${PYENV_ROOT}}" != "${PYENV_BIN_PATH}" ]; then
  # Only add to $PATH for non-system version.
  export PATH="${PYENV_BIN_PATH}:${PATH}"
fi
exec "$PYENV_COMMAND_PATH" "$@"

其中透過呼叫 pyenv which 可以得出當前使用的 Python 版本,並且取得其 bin/ 位置加入至 PATH 環境變數,最後再帶入原先帶入的參數執行。

替代方案

  • 若不希望使用 pyenv 的 Shim,也不希望看到像是 .python-version, version, PYTHON_VERSION 這樣的檔案或變數在,也可以透過 pyenv 中的 python-build 來幫助自己下載 特定的 Python 版本並解壓縮、編譯到想要的位置。
    擷取自 PyConTW'18 TP 大大的分享:
$ python-build 3.6.5 ~/.local/pythons/3.6
$ python-build 3.5.4 ~/.local/pythons/3.5
$ ln -s ~/.local/pythons/3.6/python3.6 ~/.local/bin
$ ln -s ~/.local/pythons/3.5/python3.5 ~/.local/bin
$ ln -s ~/.local/bin/python3.6 ~/.local/bin/python3
  • 若不使用 Pyenv 也完全不希望使用系統的 Python 版本,可以將下列指令放入 shell 配置檔中,同樣取自 PyConTW'18 TP 大大的分享:
python() {
  local PYTHON="$(which python)"
  if [[ "$PYTHON" == /usr/* ]];
  then
     echo "nope" >&2 | echo >/dev/null
  else
     "$PYTHON" "$@"
  fi
}

如此便可以避免呼叫到系統的 Python 版本。

團隊成員系列文

前端工程師一起來種一棵後端技能樹吧!

想盡辦法當好一個Junior Backend Developer

參考資料

其他備註


上一篇
[用舒服的姿勢開發 Python Project] Day 03 - Pyenv 基本使用
下一篇
[用舒服的姿勢開發 Python Project] Day 05 - pip 中存在的問題
系列文
用舒服的姿勢開發 Python Project7

尚未有邦友留言

立即登入留言