木兰编程语言不同运行版本的 Python sys.path 差异小结
上文中的两个问题之一,是发布到 PyPI 的版本在运行时有个测试未通过:测试/引用/引用本地包内py.ul,而开发版和木兰原始可执行文件是通过的。
由于这是第一次发觉 PyPI 发布版本的行为差异,于是优先研究。
问题描述
测试源码如下:
using test.package.module_py
test.package.module_py.talk()
在项目根目录下(后文如无特别说明,命令运行都在根目录下),有如下测试包结构:
- test
- package
- module_py.py
- package
- test_module_py.py <--- 该模块在根目录下
开发版如下运行测试无误:
$ python3 -m 木兰 测试/引用/引用本地包内py.ul
但如果安装在 PyPI 的发布版,再如下运行相同测试用例则报错(见此 issue):
$ 木兰 测试/引用/引用本地包内py.ul
没找到模块:‘test.package’
调用层级如下
见第1行:using test.package.module_py
但是,发布版如下引用当前目录下的模块并无问题:
using test_module_py
test_module_py.talk()
为何?
调查过程
由于木兰原始可执行文件运行该测试无误,于是首先对木兰重现项目用 PyInstaller 生成(参考前文)exe 文件进行测试,引用并无问题。
由于无头绪,建立最简项目重现问题,再次确认当前运行目录需添加到 sys.path 才能引用当前目录下的模块 test_module_py(在此前的 commit 中实现)。接下去围绕 sys.path 做一系列测试。
期间试着将 test 目录改名为 tes,结果引用成功。又直接使用__import__('test'),才发现在 Python3 的包路径中,原就存在一个 test 包。
结论
sys.path 在不同运行版本的区别如下:
- PyPI 发布版:虽然添加了运行当前目录到 sys.path,但位于末尾。在 sys.path 中更靠前的路径中包含 Python3 的包路径,其中恰好存在其他 test 包,而且该包中没有 package 子目录,因而报错。
- exe 发布版(用 PyInstaller 生成):sys.path 中不包含 Python3 的包路径,因而不会认错 test 包。
- 开发版:运行的当前目录位于 sys.path 第一个。sys.path 中虽也包含 Python3 的包路径,但由于顺序靠后,并不起作用,因而不认错 test 包。
由此来看重现项目两种发布版的优劣势:
- PyPI 发布版:由于自带 Python3 包路径,那么只要是当前 Python3 环境下可用的包,木兰即可引用。副效应就是万一碰到上面这样的同名包,会优先引用 Python3 自带包,而 Python 的行为是优先引用自定义包。
- exe 发布版:由于不带 Python3 包路径,木兰将不能引用 Python3 环境下安装的包。当然也就没有同名包的优先问题。但如果用户自行配置了环境变量,使得系统路径中包含 Python3 的包路径,是否会有同样问题尚待确认。