更新于 2020.12.7

这篇文章是PEP 582 的开发日志的后续,因为按照之前的实现方法,有几个缺陷:

  1. 为了可执行文件能直接全局运行,需要在文件里塞私货
  2. 需要魔改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 钩子提供了可能1site模块的执行流程大概是这样的:

image-20201126104508094

要在这里加私货,有 4 个途径:

  1. 魔改一个site.py使得python -m site的时候执行到这里
  2. 写一个.pth文件,使用import开头的行,达成执行其中代码的效果()
  3. userbase中添加.pth文件
  4. 写一个usercustomize.py
  5. 写一个sitecustomize.py

咱们挨个分析,1 和 2 都要在全局的 Python 目录中塞文件,这里有个重大的问题——文件权限,所以直接不予考虑了。 而 3 和 4 存在另外的两个问题:

  1. 执行到userbase加载时,site-packages还没加载,所以不能实现屏蔽掉site-packages的作用
  2. userbase并不是无条件加载,当检测到是venv模块创建的虚拟环境时则会跳过

所以剩下的第 5 个方案成了最终的选择,虽然它会屏蔽掉可能已经存在的sitecustomize.py,但这也是很小的问题了。

更新按 在最初其实选择的是方案 2,忽略了文件系统权限的问题,这里要感谢@Aloxaf指出了问题

于是sitecustomize.py中会执行一个顶层函数,将site-packages去掉并加载上__pypackages__目录。而用户需要将这个文件所在的目录加到PYTHONPATH中(PDM 提供了快捷命令)。

一些感想

其实这次改进要感谢 Poetry discord 里面的一个提问

image-20201126105751011

我一直都知道.pth的用法,但没有去想到可以用它来魔改 Python 解释器,只是微小的一步。有些时候要对已经实现的代码时常审视,说不定就找到了新的途径。

以上。


  1. 你们可能知道PYTHONSTARTUP也可以控制启动的行为,但这也需要额外配置,故不考虑