真实构造一个带__init__.py的包,验证import包时__init__.py的执行时机、__all__对星号导入的限定作用,以及完全不写__init__.py的命名空间包依然能被真实导入。
代码 pkg1/__init__.py / pkg1/util.py# pkg1/__init__.py print("pkg1/__init__.py: executing") from .util import helper VERSION = "1.0" __all__ = ["helper", "VERSION"] # pkg1/util.py print("pkg1/util.py: executing") def helper(): return "helper called" def _private(): return "should not leak via *" import pkg1 print(pkg1.__file__) print(pkg1.VERSION) print(pkg1.helper())
pkg1/__init__.py: executing ← 真实先执行__init__.py pkg1/util.py: executing ← __init__.py里的from .util import helper真实触发util.py执行 pkg1.__file__ → .../pkg1/__init__.py ← 包的__file__真实指向__init__.py本身 pkg1.VERSION → 1.0 pkg1.helper() → helper called
真实的顺序完全对应__init__.py里代码的写法:import pkg1触发的第一件事就是执行pkg1/__init__.py这份文件,它里面的from .util import helper又真实触发了util.py被加载一遍——所以pkg1.helper能直接用,本质上是__init__.py把子模块里的名字"搬"到了包自己的命名空间里,调用者完全不需要知道helper原本定义在util.py里。
from pkg1 import * "helper" in dir() → True ← 在__all__里 "_private" in dir() → False ← 不在__all__里,即使util.py里定义了它 "util" in dir() → False ← util子模块本身也不在__all__里,星号导入不会带进来
目录结构:nspkg/mod_ns.py(真实没有__init__.py) import nspkg.mod_ns as m print(m.X) → namespace works ← 真实成功导入 import nspkg print(nspkg.__file__) → None ← 真实没有__file__,因为没有实体的__init__.py文件 print(list(nspkg.__path__)) → ['.../nspkg'] ← 但__path__真实存在,指向这个目录本身
这是PEP 420在Python 3.3之后真实内置的行为:只要一个目录下有Python文件、但没有__init__.py,Python的默认查找器依然会真实把它当成一个"命名空间包"来对待——nspkg.mod_ns真实能正常导入,只是nspkg本身没有一份真正的__file__(它不对应任何一份被执行过的源文件),取而代之的是一个__path__,指向这个目录,供后续查找子模块用。这个特性真实常被用在"插件式"的项目结构里:多个独立分发的包可以共享同一个顶层命名空间(比如company.plugin_a和company.plugin_b分别打包发布,运行时它们的company目录会真实被合并成同一个命名空间包),代价是行为比显式__init__.py更隐晦——包本身没有一处集中的地方能放初始化逻辑或者__all__声明。
每一步都是本文上面用真实构造的包目录跑出来的原始结果。