技术 2023 年 6 月 27 日

从 Pip 迁移到 Poetry

端午节在家给开发机的系统升级到了到 Ubuntu 23.04,当时只做了一些简单的检查,没有发现问题就安心睡觉去了。
没想到,节后第一天上班,一打开 VSCode 的 Python 项目,就发现一大堆报错,顿感大事不妙。啊,Python,How old are you? (怎么老是你?)
上次升级 macOS 也是遇到了 Python 环境的问题。真让人头疼。这时候不得不祭出这张图了。
Python 环境
Python 环境
这次无论如何,也要把他理清楚,减少以后无休止的莫名其妙的问题,避免持续的流血 DEBUFF。

问题现状

冷静下来,分析了一下。现在的报错主要是依赖缺失,也就是说,之前通过 pip 安装的依赖全部都没有了。
那再安装一遍就是了。当我尝试 pip install langchain 的时候,居然报错了,如下:
pip install error
pip install error
关键信息是:This environment is externally managed╰─> To install Python packages system-wide, try apt install python3-xyz, where xyz is the package you are trying to install.
也就是说,系统级别的安装,现在需要通过 apt install python-xyz 来替代原来的 pip install xyz 了。或者就要用 Python 虚拟环境 venv。
这是为什么呢?因为默认情况下,pip 是直接安装依赖到当前 pip 对应 Python 的系统目录下的。这在存在多个 Python 版本,或者多个依赖版本时,特别混乱。
经常出现 A 项目有报错,一番操作后,解决了,但 B 项目又出现了问题。或者,明明我昨天安装了 c 依赖,怎么今天又找不到了。。等等。

Python 和 pip 的运作方式

为了从根源上解决问题,我们需要先分析一下 Python 和 pip 的运作方式。下面已 Ubuntu 23.04 为例。
对于 Python,现在一般只有 python3 命令了,python 命令对应的是 Python 2.x 版本,现在已经不维护了。这在一定程度上也简化了问题的复杂度,因此我们也没必要讨论 Python 2.x。
通过 which python3 可以查看当前 Shell 使用的 Python 可执行文件路径。对我的 Ubuntu 来说,是 /usr/bin/python3。在我的 macOS 上,由于是通过 Homebrew 安装的,它的路径是 /usr/local/bin/python3
/usr/bin/python3 是系统自带的 Python,一般不建议动它。如果要安装其他的版本,一般会安装到别的路径,然后通过修改 PATH 环境变量,优先使用自己安装的版本。(但现在也不推荐这么做了,具体看下文。)
对于 pip,也有 pippip3 两种。在过去,分别对应 Python 2.x 和 Python 3.x 两个版本,但现在,两者同时指向 Python 3.x。如果系统上同时存在多个 Python 3.x,pip 的指向可能就会乱掉。
具体看当前的 pip 指向的是哪个 Python 版本,可以通过 pip show 命令查看。比如 pip show pip,我的输出是包含了 Location: /usr/lib/python3/dist-packages 这一行,就表明,当前的 pip 对应的 Python 是 /usr/lib/python3
默认情况下,通过 pip install xyz 安装的软件包,也会安装到 /usr/lib/python3/dist-packages 目录下。当系统升级 Python 时,可能就没了。这也是导致我安装依赖全部丢失的原因。
至于 pip3,现在都没有 python2 了,你也应该忘记它。

最佳实践

但现在已经无法通过 pip install xyz 安装了,怎么办呢?通过 apt install python-xyz 不失为一种办法,但这个太不 Pythonic 了。只适合轻量级的使用,比如偶尔用一下某个 Python 编写的命令行工具,比如 httpie 之类,不适合 Python 开发。
经过这几天的探索,也算是找到了个人的最佳实践。简而言之就是使用 pipxpoetry 这两个工具,其中 pipx 用于系统级别的可执行软件包管理, poetry 用于项目的依赖管理。

pipx 的使用

pipxpip 最大的区别就是 pipx 是在一个隔离的环境里安装和运行 Python 程序。可以通过 pipx ensurepath 来确认位置。
pipx ensurepath
pipx ensurepath
这个隔离的环境其实也是一个自动创建的 Python 虚拟环境,默认位于 ~/.local/pipx/venvs。由于不对外暴露太多细节,所以不适合用来开发,只适合用来运行命令行程序。
安装 pipx 推荐使用系统级别的包管理工具,比如 Ubuntu 上的 apt 或者 macOS 上的 brew。因为,这真的只是用来进行一些系统级别的包管理,和 Python 开发几乎没有任何关系。
唯一的关系就是,Python 开发所需的 poetry 需要通过 pipx 来安装。执行:
pipx install poetry
使用 pipx list 可以列出所有安装的 Python 包,并且只会列出直接依赖,所以列表非常简洁。
pipx list
pipx list

poetry 的使用

使用 Poetry 很简单,基本只要 3个命令,官方文档也很详细,这里不赘述。
poetry init     # 初始化项目
poetry add xyz  # 增加依赖
poetry install  # 安装所有依赖
Poetry 通过标准的 pyproject.toml 文件描述项目和依赖,通过 poetry.lock 文件记录依赖的版本信息。Poetry 自身的配置信息报错在 ~/.config/pypoetry/config.toml
默认情况下,Poetry 就很好用了,但我更喜欢通过 poetry config virtualenvs.in-project true 将虚拟环境设置为 in-project,这样就实现了类似 node_modules 的效果,更好管理,不用担心项目多了之后虚拟环境乱糟糟的无法管理。
poetry venv
poetry venv

为什么不用 Pipenv

Pipenv 也是一个优秀的 Python 环境和依赖管理工具,但是相对于 Poetry,有一个缺点难以忍受,就是 locking 比较频繁,且非常慢,尤其在国内网络环境下。为了生活更美好,请用 Poetry。

为什么不用 Conda

我觉得 Conda 是一个非常反程序员的东西,本来想说反人类的,想了想还是改成反程序员。因为它不是一个面向程序的产品,也不符合程序员的工作逻辑。这东西制造出来的麻烦远比它带来的便利多,而且多很多。具体可以看上面的那张图。
Poetry 完全可以解决 Conda 视图解决的问题,并且做得更好。为了生活更美好,请立即停止使用 Conda。

总结

通过以上的步骤,现在我们的系统里只有一种 python3pip 了。你几乎不会直接接触他们,所以直接放养就好,不用管它们的死活。
如果你要安装一些 Python 写的命令行工具,请使用 pipx。如果需要进行 Python 开发,请使用 poetrypipx 是用户级别的,poetry 是项目级别的,它们都不会都系统造成什么影响,非常符合 UNIX 哲学。
其他什么都不要用。