Python中__init__.py使用别名导入子模块后原模块名仍暴露在包命名空间的疑问
这确实是个容易让人困惑的点,我刚接触Python包导入机制的时候也踩过类似的坑——这不是Python的bug,而是包导入时的特殊设计导致的,咱们一步步拆解来理解:
为什么会出现这种情况?
当你在包的__init__.py里执行from . import submodule as sm时,Python的包加载逻辑是这样的:
- 首先找到并加载
submodule子模块,自动将其注册到包的命名空间(也就是package.__dict__),所以submodule这个名字会出现在包的dir()结果里; - 然后再将这个已加载的子模块的引用,赋值给你指定的别名
sm。
这和普通脚本里的from package import submodule as sm逻辑完全不同:后者是从包的命名空间里取出已存在的submodule引用,赋值给当前脚本的sm,但不会在脚本命名空间里创建submodule这个名字——两者的场景一个是“在包内部加载子模块”,一个是“从外部导入已存在的模块引用”,逻辑自然有区别。
怎么实现你想要的效果?
如果你希望test.py无法访问package.submodule,可以用以下两种方法:
方法1:导入后主动删除原模块名
在__init__.py里导入别名后,手动删除submodule这个名字的引用:
from . import submodule as sm del submodule # 移除包命名空间里的原模块名 print(dir())
此时运行test.py,尝试访问package.submodule会抛出AttributeError: module 'package' has no attribute 'submodule',完全符合你的预期。
方法2:用importlib手动加载子模块
使用importlib.import_module加载子模块,这种方式不会自动将原模块名注册到包的命名空间:
import importlib sm = importlib.import_module(".submodule", __name__) print(dir())
运行后dir()里只会有sm,test.py同样无法访问package.submodule。
验证效果
用方法1修改后,__init__.py的dir()输出会变成:
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', 'sm']
test.py运行时会直接报错,完美实现“隐藏原模块名”的需求。
备注:内容来源于stack exchange,提问作者Amess

