更新于 2020.12.7
这篇文章是PEP 582 的开发日志的后续,因为按照之前的实现方法,有几个缺陷:
- 为了可执行文件能直接全局运行,需要在文件里塞私货
- 需要魔改
lib
目录下的site.py
,可能造成冲突
但是,非常 Exciting 地,现在 PDM 的 PEP 582 可以说是完全态了!先看下 Demo:
依赖被安装在了隔离的目录__pypackages__
,但运行却可以通过全局解释器python
运行。
- 没有
activate
,改任何 shell 变量 - 没有用一个包装过的
python
可执行文件 - 没有
pdm run
前缀
为了证明依赖确实没有被安装在全局解释器下,我演示了在别的目录import flask
返回失败。简而言之,就是用全局的解释器,加载隔离的依赖目录,无限接近 Node.js 的体验。你所要做的,只是export PYTHONPEP582=1
,但其实这也是一个 feature 开关,未来可能不再需要。
这是怎么做到的
难道我魔改了 Python 解释器?不不不,秘诀还是在 Python 的site
模块中。前文提到过,site
模块有个最大的特点,就是除非你显式加上-S
参数,它会在 Python 启动时作为模块执行,这就为一些稀奇古怪的 startup 钩子提供了可能1。site
模块的执行流程大概是这样的:
要在这里加私货,有 4 个途径:
- 魔改一个
site.py
使得python -m site
的时候执行到这里 - 写一个
.pth
文件,使用import
开头的行,达成执行其中代码的效果() - 在
userbase
中添加.pth
文件 - 写一个
usercustomize.py
- 写一个
sitecustomize.py
咱们挨个分析,1 和 2 都要在全局的 Python 目录中塞文件,这里有个重大的问题——文件权限,所以直接不予考虑了。 而 3 和 4 存在另外的两个问题:
- 执行到
userbase
加载时,site-packages
还没加载,所以不能实现屏蔽掉site-packages
的作用 userbase
并不是无条件加载,当检测到是venv
模块创建的虚拟环境时则会跳过
所以剩下的第 5 个方案成了最终的选择,虽然它会屏蔽掉可能已经存在的sitecustomize.py
,但这也是很小的问题了。
更新按 在最初其实选择的是方案 2,忽略了文件系统权限的问题,这里要感谢@Aloxaf指出了问题。
于是sitecustomize.py
中会执行一个顶层函数,将site-packages
去掉并加载上__pypackages__
目录。而用户需要将这个文件所在的目录加到PYTHONPATH
中(PDM 提供了快捷命令)。
一些感想
其实这次改进要感谢 Poetry discord 里面的一个提问
我一直都知道.pth
的用法,但没有去想到可以用它来魔改 Python 解释器,只是微小的一步。有些时候要对已经实现的代码时常审视,说不定就找到了新的途径。
以上。
Footnotes
- 你们可能知道
PYTHONSTARTUP
也可以控制启动的行为,但这也需要额外配置,故不考虑 ↩