Python 3中导入包内子模块后,主模块为何能访问未导入的同包子模块?
原因解析
1. Python包的模块挂载逻辑
当你导入mylib.foo时,Python会先初始化mylib这个包对象(哪怕mylib下的__init__.py是空文件)。随后mylib.foo里的import mylib.bar会触发mylib.bar模块的加载,并且会把bar模块直接绑定到mylib这个包对象的属性上。
换句话说,mylib作为一个全局可见的包容器,只要同包内任意模块加载了其他子模块,这些子模块就会成为mylib的属性。外部代码只要能访问mylib,就能直接调用这些已被加载的子模块。
2. numpy与mylib.bar的本质区别
mylib.foo里的import numpy是将numpy模块绑定到mylib.foo的局部命名空间,既不会挂载到mylib包对象上,也不会进入main.py的全局命名空间,所以main.py未显式导入时无法访问。
而import mylib.bar是将bar模块绑定到mylib包对象上,相当于给mylib这个容器新增了一个bar属性,因此main.py能通过mylib.bar直接访问。
3. 平级模块无此现象的原因
当foo.py和bar.py与main.py平级时,foo.py里的import bar会将bar加载到全局模块字典,但不会自动注入到main.py的全局命名空间。main.py要访问bar必须自己显式导入,或者通过sys.modules['bar']获取——不存在像mylib这样的父包容器来统一挂载所有加载过的同目录模块,因此不会出现包结构中的自动访问现象。
验证方式
你可以在main.py中添加print(dir(mylib)),会发现bar已经出现在mylib的属性列表中;而平级模块结构下,打印globals()不会看到bar的身影(除非自己显式导入)。
内容的提问来源于stack exchange,提问作者jopun

