管理 Python 多版本,pyenv 用起来

管理 Python 多版本,pyenv 用起来

介绍

学习使用pyenv在本地安装多个 Python 版本,这样既不影响工作,也不影响生活~

pyenv 可让你轻松地在多个 Python 版本之间切换。它简单、不引人注目,并且遵循 UNIX 的单一职责的传统,可以很好地完成一件事。

安装 pyenv

  1. 在 mac 上,使用brew
$ brew install pyenv
  1. 在 shell 中配置 pyenv 的初始化
$ echo 'eval "$(pyenv init --path)"' >> ~/.zprofile

更多安装方式见官网安装文档

pyenv 常用命令

查看所有可安装的 Python 版本

$ pyenv install -list
Available versions:
  2.1.3
  2.2.3
  2.3.7
  2.4.0
  2.4.1
  2.4.2
  ...
  3.9.1
  3.9.2
  3.9.3
  3.9.4
  3.9.5
  3.10.0b2
  3.10-dev
  3.11-dev

查看已安装的 Python 版本

$ pyenv versions
  system
* 3.7.10 (set by /Users/zioyi/.pyenv/version)

安装指定 Python 版本

$ pyenv install 3.10.0b2
Downloading 3.10.0b2...
-> https://downloads.python.org/3.10.0b2.tar.bz2
Installing 3.10.0b2...
Installed 3.10.0b2 to /Users/zioyi/.pyenv/versions/3.10.0b2

全局环境指定 Python 版本

$ pyenv global 3.10.0b2
$ python
Python 3.10.0b2 (default, Jul 29 2021, 09:57:10) [Clang 12.0.5 (clang-1205.0.22.9)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> 

当前 shell 下指定 Python 版本

$ pyenv local 3.7.10
$ python
Python 3.7.10 (default, Jul 29 2021, 09:57:10) [Clang 12.0.5 (clang-1205.0.22.9)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> 

备注:local 的优先级高于 global,后面的原理以后讲到

卸载已安装的 Python 版本

$ pyenv uninstall pypy3.7-7.3.5
pyenv: remove /Users/zioyi/.pyenv/versions/pypy3.7-7.3.5? [y|N]y
pyenv: pypy3.7-7.3.5 uninstalled

更多用法见官网命令手册

pyenv 的原理

实际上, pyenv 的秘密就藏在安装 pyenv部分的第二步:初始化 pyenv

eval "$(pyenv init -)"

因为这行命令写在.zprofile文件中,所以在每次登录 shell ,都会去执行,它的效果是在PATH中加入 pyenv 相关的路径

$ echo $PATH
***:/Users/zioyi/.pyenv/shims:/Users/zioyi/.pyenv/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:***

Linux 执行命令时,是依次遍历 PATH 环境变量的每个路径,查找所执行的命令。当在某个目录下找到第一个匹配时即停止遍历,所以 PATH 环境变量中,前面的路径比后面的路径具有更高的优先级。

系统默认的 Python 一般是在/usr/bin目录下面,而 pyenv 的路径/Users/zioyi/.pyenv/shims在其之前,所有当我们在 shell 中去执行 Python 命令时,会现在 pyenv 的目录里去找

$ which python
/Users/zioyi/.pyenv/shims/python

我们将 shim 称作垫片,它就像是 shell 和 Python 之间的一层代理,pyenv 在 ~/.pyenv/shims 目录下创建了各种 Python 版本相关命令的垫片

$ ls ~/.pyenv/shims
2to3                     flask                    jupyter-bundlerextension jupyter-troubleshoot     pycodestyle              pytest-bdd               python3.7-gdb.py         rst2odt_prepstyles.py
2to3-3.10                idle                     jupyter-console          jupyter-trust            pydoc                    pytest-benchmark         python3.7m               rst2pseudoxml.py
2to3-3.7                 idle3                    jupyter-kernel           libpypy3-c.dylib         pydoc3                   python                   python3.7m-config        rst2s5.py
__pycache__              idle3.10                 jupyter-kernelspec       mako-render              pydoc3.10                python-config            pyvenv                   rst2xetex.py
coverage                 idle3.7                  jupyter-migrate          pep8                     pydoc3.7                 python3                  pyvenv-3.7               rst2xml.py
coverage-3.7             iptest                   jupyter-nbconvert        pip                      pyflakes                 python3-config           rst2html.py              rstpep2html.py
coverage3                iptest3                  jupyter-nbextension      pip3                     pygmentize               python3.10               rst2html4.py             send2trash
cpuinfo                  ipython                  jupyter-notebook         pip3.10                  pypy                     python3.10-config        rst2html5.py
easy_install             ipython3                 jupyter-qtconsole        pip3.7                   pypy3                    python3.10-gdb.py        rst2latex.py
easy_install-3.7         jsonschema               jupyter-run              py.test                  pypy3.7                  python3.7                rst2man.py
flake8                   jupyter                  jupyter-serverextension  py.test-benchmark        pytest                   python3.7-config         rst2odt.py

所以当我们执行 python 相关的命令时,实际执行的是这些垫片。这些垫片的内容都是相同的:

$ cat ~/.pyenv/shims/python
#!/usr/bin/env bash
set -e
[ -n "$PYENV_DEBUG" ] && set -x

program="${0##*/}"

export PYENV_ROOT="/Users/zioyi/.pyenv"
exec "/usr/local/opt/pyenv/bin/pyenv" exec "$program" "$@"

从脚本内容可以看出,当我们执行某个命令 program "param1" "param2" ……时,实际执行的是 pyenv exec "program" "param1" "param2" ……。
例如执行python -V,实际执行的是pyenv exec python -V

pyenv exec命令中,首先会调用pyenv version name确定 python 版本或虚拟环境版本,具体查找规则为:
管理 Python 多版本,pyenv 用起来

pyenv exec命令中,会再调用pyenv which确定可执行文件 program 的路径。如果前面pyenv version name确定了 Python 版本或虚拟环境版本,则使用<pyenv 安装路径>/versions/<版本号>/bin/<程序名><pyenv 安装路径>/versions/<版本号>/env/<虚拟环境名>/bin/<程序名>,否则遍历所有版本号的安装路径,按顺序取第一个匹配到的可执行文件。

确定与版本号对应的可执行文件路径 path 之后,执行以下命令:

exec -a program "$path" "param1" "param2" 
# 注:即执行 "path" "param1" "param2",并使用 program 作为程序名,程序名即 shell 中的 $0,python 中的 sys.argv0

例如执行python -V,确定 pyenv 版本为 3.10.0b2,对应可执行文件为~/.pyenv/versions/3.10.0b2/bin/python,则执行命令为:

exec -a python ~/.pyenv/versions/3.10.0b2/bin/python -V

以上就是 pyenv 执行命令的基本原理了

总结

pyenv 可以很好的帮助我们管理 Python 的多版本问题。当不同的工程项目所需的 Python 版本不同时,可以通过 pyenv 来方便管理;当我们想尝鲜 Python3.10 而又担心污染开发环境时,也可以使用 pyenv。

上一篇:python多项目管理


下一篇:0、Python环境安装