wsl2 环境下 pyenv 安装配置过程

最近为了做作业需要安装不同版本的 python,于是考虑使用 pyenv 来管理这些不同的版本。尽管一开始我觉得这个工具用起来非常简单方便,在配置过程中也出现了很多意想不到的问题,花费了我很多时间,所以在这里把整个配置的过程记录下来。

这篇文章分为两个部分,第一部分按顺序介绍 pyenv 正确配置的过程,在第二部分针对配置过程可能遇到的问题给出解决方案。

pyenv 配置过程

pyenv 的安装

只需要按照 官网 上的流程一步步做,就可以顺利安装 pyenv 了。

$ git clone https://github.com/pyenv/pyenv.git ~/.pyenv
$ echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bash_profile
$ echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bash_profile
$ echo -e 'if command -v pyenv 1>/dev/null 2>&1; then\n
eval "$(pyenv init -)"\nfi ' >> ~/.bash_profile
$ exec "$SHELL"

其中,bash_profile 需要根据不同的环境设置,在 bash 中为 .bashrc,在 zsh 中为 .zshrc,等等。

具体 python 版本的安装

我做作业是需要用到 3.6.8 版本的 python,所以这边以这个版本为例。由于 pyenv 是以源码编译的方式安装 python,所以需要首先下载好一些编译工具(如 gcc)以及安装必要的依赖。

$ sudo apt-get update
$ sudo apt-get install libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev liblzma-dev

官网 上给出了完整的依赖,但有些依赖在 wsl2 的系统上已经预装了,有些依赖会需要下载 python2.7,我就选择了手动确定应该安装哪些包,而没有一股脑全部装了。

安装好之后需要手动设定 C 编译器的选项,以便编译器可以找到我们下载好的头文件以及库文件。

$ CFLAGS=-I/usr/include/openssl 
$ LDFLAGS=-L/usr/lib

然后就可以安装 python3.6.8 了。

$ pyenv install 3.6.8

常见问题

pyenv 无法找到 system 版本的 python

下载好后,我就迫不及待将当前版本切换到了 python3.6.8 的版本

$ pyenv global 3.6.8

此时再使用 pyenv versions 查看所有的版本

$ pyenv versions

* 3.6.8 (set by /home/username/.pyenv/version)

我系统原本自带的 python 不见了!进行一番调研才明白,在高版本的 ubuntu 中,python 可执行文件名一般都叫 python2 或者 python3,而 pyenv 是通过 python 这个名字来寻找系统的 python 可执行文件。只需要给 python3 添加一个软链接指向 python 就可以了。

在 Ubuntu20.04 LTS 及更新的版本中,可以直接下载官方插件 python-is-python3(😅

$ sudo apt-get install python-is-python3

而在此前的版本中,则需要手动添加软链接

$ ln -s /usr/bin/python3 /usr/bin/python

此时再执行 pyenv versions 就又可以发现系统自带的 python 了。

$ pyenv versions

  system
* 3.6.8 (set by /home/username/.pyenv/version)

ERROR: The Python ssl extension was not compiled. Missing the OpenSSL lib?

在运行 $ pyenv install 3.6.8 后,可能会看到如下错误提示:

$ pyenv install 3.6.8

Downloading Python-3.6.8.tar.xz...
-> https://www.python.org/ftp/python/3.6.8/Python-3.6.8.tar.xz
Installing Python-3.6.8...

WARNING: The Python bz2 extension was not compiled. Missing the bzip2 lib?
WARNING: The Python readline extension was not compiled. Missing the GNU readline lib?
ERROR: The Python ssl extension was not compiled. Missing the OpenSSL lib?

这三个错误的原因是相似的,都是由于当前的机器没有安装对应的 C 语言库,而 pyenv 是从源码编译来安装 python 的。只要按照此前的步骤安装好了相应的依赖就不会有这个问题了。

$ sudo apt-get install libssl-dev libbz2-dev libreadline-dev
$ CFLAGS=-I/usr/include/openssl 
$ LDFLAGS=-L/usr/lib

然后再重新安装就好了。

$ pyenv install 3.6.8

zipimport.ZipImportError: can’t decompress data; zlib not available

这个错误与上面也是类似的,安装好缺少的依赖即可。

$ sudo apt-get install zlib1g-dev

UserWarning: Could not import the lzma module.

做作业需要用到 pandas 这个库,通过 pip 我很方便地就完成了安装。然而当我试图在 python 中引入这个库时,却产生了这个问题:

>>> import pandas

/home/username/.pyenv/versions/3.6.8/lib/python3.6/site-packages/pandas/compat/__init__.py:120: UserWarning: Could not import the lzma module. Your installed Python is incomplete. Attempting to use lzma compression will result in a RuntimeError.
  warnings.warn(msg)

这里的问题也是缺少此前列举出的依赖,安装好缺少的依赖项,并且重新安装 3.6.8 版本的 python 就可以了。

$ sudo apt-get install liblzma-dev
$ pyenv uninstall 3.6.8
$ pyenv install 3.6.8

重新安装好 pandas 后,再进行测试可以看到已经没有这个问题了。

>>> import pandas
>>> pandas
<module 'pandas' from '/home/username/.pyenv/versions/3.6.8/lib/python3.6/site-packages/pandas/__init__.py'>

No module named _sqlite3

在首次启动 jupyter notebook 时,遇到了这个问题:

$ jupyter notebook

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python3/sqlite3/__init__.py", line 24, in <module>
    from dbapi2 import *
  File "/usr/local/lib/python3/sqlite3/dbapi2.py", line 27, in <module>
    from _sqlite3 import *
ImportError: No module named _sqlite3

这个错误与上面也是类似的,是缺少 sqlite3 这个库。安装好之后再重新安装 python 即可。

$ sudo apt-get install libsqlite3-dev
$ pyenv uninstall 3.6.8
$ pyenv install 3.6.8

安装好 jupyter notebook 再启动可以发现已经可以正常使用了。

jupyter 中 python 内核版本与 pyenv 配置不匹配

pyenv 安装好以后,在命令行界面的一切看起来都非常完美,不同版本的 python 被 pyenv 管理得井井有条,无论是 python 的版本还是当前版本下安装的各种库都与 pyenv 中的设置完美匹配。但是当我在 jupyter notebook 中测试时,就产生了下面的问题。

首先在命令行打开 jupyter notebook:

$ jupyter notebook

在 jupyter notebook 中输入以下语句

In[1]: import sys
       sys.version

Out[1]: '3.8.2 (default, Jul 16 2020, 14:00:26) \n[GCC 9.3.0]'

In[2]: !pyenv versions

        system
      * 3.6.8 (set by /home/xun34/.pyenv/version)

这可真是见了鬼了!pyenv 中明明设置当前的 python 版本为 3.6.8,在 jupyter 中却还是在使用 system python 的内核。除此以外,我在 3.6.8 版本下安装的各种库在 jupyter 环境中也是找不到的。

在网上进行了(好大)一番搜索,才发现原来是因为我在 wsl2 环境中安装了多个不同版本的 jupyter notebook,而它们都被添加到了环境变量里边,可以通过 whereis 命令查看一下

$ whereis jupyter

jupyter: /home/username/.local/bin/jupyter /home/username/.pyenv/shims/jupyter

果真是存在两个 jupyter 版本的,而它们各自是和安装它们的 python 内核关联的。想要在 jupyter notebook 中使用新安装的 python 3.6.8,只需要显式指定该版本的 jupyter notebook 打开即可。

$ /home/username/.pyenv/shims/jupyter notebook

此时就没有上述的问题了。

BONUS

一开始打开 jupyter notebook 时,会很快地闪过一个错误信息,我通过屏幕截图巧妙地捕捉到了这个信息(x),大约长下面这样:

jupyter报错信息

我以为这个错误信息和 jupyter 没有使用 pyenv 配置的 python 版本这个问题有关,因此就花了很大的精力研究这个问题,后来才发现它们其实没有任何关系😢。

产生这个问题是由于 jupyter notebook 试图在 wsl2 中打开浏览器,因此简单地使用

$ jupyter notebook --no-browser

然后再手动在 Windows 端打开浏览器就可以了。或者也可以通过生成 jupyter notebook 的配置文件:

jupyter notebook --generate-config

然后修改该配置文件,使得

c.NotebookApp.use_redirect_file = False

最后将 Windows 端浏览器的路径加入到 .bashrc 文件中

export BROWSER='/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe'

$ source .bashrc

此时就可以自动打开浏览器了!

参考

[1]. pyenv 安装步骤
[2]. pyenv 安装常见问题解答
[3]. pyenv 无法找到 system 版本的 python
[4]. ERROR: The Python ssl extension was not compiled. Missing the OpenSSL lib?

[5]. jupyter not using version set by pyenv
[6]. No module named _sqlite3
[7]. Start : This command cannot be run due to the error: The system cannot find the file specified
[8]. UserWarning: Could not import the lzma module.