在上周的周记中我记了一句:
- pdm 提了几个 issue,都和 debian 系统的 python 有关,i hate it
本文是对这句话的一个扩展。作为一个 Python 打包工具的开发者,非常痛恨 Debian 系统,所以我在回复 laixintao 时说道: Python 打包系统的混乱,Debian 系统是要居大功的。其实不止 Debian 是如此,下面开始吐槽,非常不严谨,吐槽完我就跑。
Python 中的 Install Scheme
Install Scheme 的概念,中文大概是翻译成「安装蓝图」?意思就是提供了一个路径的集合,告诉 Python 包的安装器(如 pip
),什么文件应该放到哪个路径下。 本文只打算讨论 purelib
和 platlib
,也就是 Python 库应该放到哪里。Install Scheme 可以通过以下函数获得:
import sysconfig
install_paths = sysconfig.get_paths()
print(install_paths['purelib'])
正常情况下,比如我之前的文章提到的一样,Python 库是放到:
- Posix:
$path_prefix/lib/pythonX.Y/site-packages
- Windows:
$path_prefix/Lib/site-packages
$path_prefix
是 Python 解释器所在的路径前缀。在这里我们先请优秀学生 Windows 回到座位上,来说说 Posix 的问题,比如 Python 路径是 /usr/bin/python3.9
,那么 /usr
是路径前缀,上述路径则变为 /usr/lib/python3.9/site-packages
。
Debian:事实并非如此
基本所有的 Linux 发行版都会自己打包 Python 库,他们不信任 PyPI,所有用到的 Python 库都拿源码下来,自己打包。那问题来了,那么多 Python 包,那么多 Python 版本,怎么打,怎么安装?他们发现了一点:纯 Python 库是比较兼容的,不需要每个 Python 版本都打一个包,只需要整个 Python 3 打一个包就可以了,所以有 python3-pip
, python3-requests
这种命名方式的包。安装的时候,也把它们放在一个地方,所有 Python 3 版本都可以用。其他不是纯 Python 的包,再分版本存放。(这段是我臆想,不严谨)
所以,在 Debian 上,就有了下面三个路径存放 Python 库:
/usr/lib/python3/dist-packages
放apt
安装的纯 Python 库/usr/lib/python3.9/dist-packages/
放apt
安装的带扩展的 Python 库/usr/local/lib/python3.9/dist-packages/
放pip3
安装的 Python 库
这属于自己的设计了,那么就需要改安装库和导入库的逻辑。Debian 维护了一系列的补丁 来干这件事,改完之后,sys.path
会包含上面三个路径,site-packages
的路径从中去除了,而 pip3
也会安装包到第三个路径。所以要记住,发行版上自带的 Python 和 pip 都是特制的,你用从官网和 PyPI 上下载的去替换是会出问题的。
OK,这没问题,也能接受,反正你自己魔改自己发布到 apt
,只要闭环了用户是感知不到变化的。但问题是,这些补丁是不完备的!。为了改默认的 sys.path
,Debian 只修改了 site
模块,以及 distutils
1 模块,而没有修改上面的 sysconfig
模块,而 distutils
模块已经在 PEP 632 里被 Deprecated 了,官方推荐的替代就是 sysconfig
。这个问题随着 Python 3.10 的发布而暴露了出来,Debian 的维护者也意识到这个问题,最新的补丁里已经修改了 sysconfig
,但不是很小心,造成了一些回归问题。这样的后果就是,如果用户切换发行版的不同版本,以及 Python 3.10 或 3.10 以下,这个 Install Scheme 安装到哪里完全捉摸不定,不可预测,那么包管理器就很难做了(还能怎样,当然是原谅(兼容)啊,难道威胁用户,不许用 apt
的版本吗?)
到底有多捉摸不定?
我做了一个测试,分别测试 Python 3.9 和 3.10,以及 pip
使用 apt
的版本和 get-pip.py
安装的版本,在旧版本 Debian2 和 debian:testing
中获取 Install Scheme 的值。
Image | Python 3.9 | Python 3.10 |
---|---|---|
ubuntu:focal | sysconfig: 未修改site: 已修改distutils: 已修改get-pip.py 安装的 pip 把库安装到 site-packages下 | sysconfig: 已修改3site: 已修改distutils: api 已移除get-pip.py 安装 pip 失败 |
debian:testing | sysconfig: 未修改site: 已修改distutils: 已修改get-pip.py 安装的 pip 把库安装到 site-packages下 | sysconfig: 已修改site: 已修改distutils: 已修改get-pip.py 安装的 pip 把库安装到 dist-packages下 |
这里已修改是指返回 dist-packages
的路径,而未修改是指返回 site-packages
的路径。测试脚本见仓库。
可以看到在 Python < 3.10 上虽然补丁不完备,行为倒还是统一的,但在 Python 3.10 上就出幺蛾子了,简直就是一团乱麻,Python 环境太难了。
Bonus: MacOS 上的 Python,取决于它是不是 framework,安装路径也有区别,但这已经在 CPython 的标准库中得到支持,不是用补丁方式解决的。 所以大家对下面这个主张没有异议了吧:
Windows 上的 Python 环境是最清爽干净的。
如何避坑?
两条建议:
- 如果要在容器中使用 Python,用
python
系列的镜像。那里面的 Python 是标准化的,不是特制的。 - 如果用户系统是 Debian 系,不要在系统路径中安装包,对,
--user
都不行,甚至python3-pip
都不要装以绝后患。而应该使用虚拟环境(python3-venv
包)和pipx
。或者用 PDM 或 Poetry 这种包管理工具。因为只有在虚拟环境中,Python 库的安装路径永远是site-packages
,无论在哪个系统上。
一些 GitHub issues
- deadsnakes/issues#182
- pypa/pip#10978
Footnotes
distutils.sysconfig
中也包含一些和sysconfig
模块中作用类似的函数。 ↩- 因为 deadsnakes ppa 只支持 ubuntu,我在这里用的其实是
ubuntu:focal
镜像。 ↩ - 在 focal 上没有 Python 3.10 的系统包,所以这里是从 deadsnakes ppa 安装的。 ↩