python import坑总结
如果所有代码都在同一个目录,一切都简单而美好。
然而一旦源码分目录,目录内的代码需要让目录外访问,那就没有完美方案。
pf
│ m4.py
│ __init__.py
├─pa
│ m1.py
│ m2.py
│ __init__.py
└─pb
m3.py
__init__.py
m2引用m1
m3引用m2
m4引用m2
问题的根源在于:
1 每次我们写import xx的时候,注意此时开头没有./../...这样的相对路径,都是相对于__main__的路径的,不是run python时的启动路径的,也不是文件自身。也就是说,从m3和m4分别作为__main__引用m2时,m2内不能简单写from m1。因为pb和pf内都没有m1。
2 当我们写from .xx/..xx import yy时,是相对于文件自身,但是这个文件就不能直接作为__main__直接执行了。因为./..要先查找上级,然而__main__没有上级,本身就是根了。这是个必须面对的问题,直接执行的能力对于小项目,或者就是日常实验的小玩具,是很方便的。或者从toy逐步长成project是很自然的。
方案1:
m2内写from pa.m1
好处,m4都能引用m2。
代价1,m2文件对pa目录的名称产生了依赖,甚至依赖于直到自己在工程中的完整层级路径,即使保持m1在m2同目录内。
代价2,m2本身不能作为__main__直接执行了,因为sys.path包pa含但不包含pf,在pa路径下是找不到pa自己的。
代价3,m3依然不能引用m2,因为pb内没有pa。
方案1a:
在方案1的基础上,修正代价2,3。在m2内的最开始增加代码:
import sys
import os
sys.path[0]=os.path.abspath(".")#不替代的话,append,insert也行,重名问题。
并且,所有__main__都在pf这一级执行。本质是将import的根路径从__main__替换成了python的启动路径。
这使得当我们在pf内执行python pa/m2.py是,sys.path中不只有pa还有pf。
注意重名问题。
方案2:
m2内写from .m1。
好处,m2不需要知道m1所在的路径叫啥名了。只要m1和m2保持在同一目录就行。
代价,m2彻底不能作为__main__执行了,根没有父也就没有兄。原直接执行功能需要挪到新文件中。
探讨,这里如果python能像python -m一样,虚拟一个包含所有同级module的父节点就好了,这是最接近大家直观需求和其他语言简单情况的行为;以文件的视角思考,而非以项目根目录的角度思考。
方案3:
使用namespace
未完待续。。。