如何优雅的管理本地多版本的 Python

前言

最近在本地尝试将某个项目迁移至 Python3.7.0 ,但是本地还有其他项目依赖 3.6.5 ,加上系统自带的 Python2,本地已经有3个不同版本的Python了。第一次我是通过官方的.dmg包安装 Python3.7.0 的,但之后发现它把 Python 安装到了:/Library/Frameworks/Python.framework/Versions ,但是我本地之前安装的 3.6.5 和 2.7.15 都是通过 Homebrew 安装的,卸载、更新、设置软连接都很方便。Homebrew安装的默认在 /usr/local/Cellar/python 中。

这样当你切换本地 python 的版本时就很麻烦,要重设一些软连接,python\python3\pydoc\pip3… 当然我知道项目内部可以用虚拟环境来管理 Python 版本。但是不可避免一些常用的软件的 Python 依赖出现问题。比如 IPython\legit\Emacs 等等,当我手动升级了版本后,这些包(软件)常常会报错,告诉你 python3.6.5 或者 python3.7.0 找不到了。每次修复软连接就很烦。

解决方案

我的解决方案就是:统一使用 Homebrew 来管理 Python 版本。只要本地安装过旧版本,没有执行brew clean等命令(即 /usr/local/Cellar/python/ 路径下有多个版本的文件夹,如 3.6.5 、3.7.0 等),就可以使用命令brew switch python [versions] 来切换 Python 的版本了。如:

$ brew switch --help
brew switch formula version:
Symlink all of the specific version of formula's install to Homebrew prefix.

$ brew switch python 3.7.0 763ms
Cleaning /usr/local/Cellar/python/3.7.0
Cleaning /usr/local/Cellar/python/3.6.5_1
25 links created for /usr/local/Cellar/python/3.7.0

如何安装旧版本的Python

当然有的小伙伴会说,使用switch命令会报错。因为/usr/local/Cellar/python/文件夹下没有其他版本的 Python ,Homebrew自然也就找不到了。所以该怎么使用Homebrew安装旧版本的 Python 呢?

首先要先知道 Homebrew 的工作方式。当你执行 brew install python时,实际是执行$ brew install https://github.com/Homebrew/homebrew-core/blob/master/Formula/python.rb 。所以我们可以通过替换其 URL 地址来安装旧版本。 Homebrew既然是使用Git来管理更新包的,那就可以查看python.rb的历史:

python.rb

如果提示无法查看可以clone到本地使用以下命令查看:

git log master -- Formula/python.rb

例如 Python3.6.5 的安装方法为:

$ brew unlink python
$ brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/e128fa1bce3377de32cbf11bd8e46f7334dfd7a6/Formula/python.rb

需要先运行 brew unlink python 来删除软连接。否则Homebrew会因为已经安装了 3.7.0 导致安装失败。

安装成功之后就可以优雅的使用brew switch python 3.7.0 或者 brew switch python 3.6.5 愉快的切换版本了。

使用 Pyenv 管理本地多版本的 Python

使用 Homebrew 管理 Python 版本也有很多局限性。例如切换 Python 版本后会导致原来一些依赖 Python 特定版本的应用无法使用。比如 brew 安装的 pipenv 、IPython 等。替代方案是使用 Pyenv 维护本地不同的版本。

最佳实践

我认为的最佳实践是在本地使用Homebrew安装 python@2python(如果需要 Python2 的话)。python即为Homebrew Python3 的最新版本。当需要其他特定版本 Python3 的时候,使用特性版本对应的 URL 安装。之后使用 brew switch命令随时切换(同样适用于其他 Homebrew 管理的软件)。

以安装 Python2.7.15、Python3.6.5、Python3.7.0 举个例子:

# 删除之前安装的所有 Python 和相关的软连接
$ brew unlink python@2
$ brew unlink python
$ rm -rf /usr/local/Cellar/python/
$ sudo rm -rf /Library/Frameworks/Python.framework/Versions/x.x
$ sudo rm -rf "/Applications/Python x.x"
$ cd /usr/local/bin/
$ ls -l /usr/local/bin | grep '../Library/Frameworks/Python.framework/Versions/x.x' | awk '{print $9}' | tr -d @ | xargs rm

# 安装 Python2.7.15
$ brew reinstall python@2

# 安装最新版本的 Python3
$ brew reinstall python

# 安装 Python3.6.5
$ brew unlink python
$ brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/e128fa1bce3377de32cbf11bd8e46f7334dfd7a6/Formula/python.rb

# 切换 Python3 的版本为 3.7.0
$ brew switch python 3.7.0

# 切换 Python3 的版本为 3.6.5
$ brew switch python 3.6.5

相关软连接对应关系如下(Python3的版本为3.7.0为例):

  • python –> python2.7.15
  • pip –> pip for python2.7.15
  • python3 –> python3.7.0
  • pip3 –> pip for python3.7.0

如果使用 Pyenv 安装了其他小版本的 Python ,可以考虑将对应的 Python 解释器连接至 /usr/local/bin 中,便于 pipenv 创建虚拟环境时通过 --python 来指定版本。不过用 Pyenv 来管理虚拟环境也行。

PS:最终我选择了 Golang , 终于没有这些恼人的版本问题了😂。