__init__.py 文件非强制但关键:存在即激活传统包,支持导入、相对导入和初始化;为空亦有效;缺失则常规导入失败,仅 namespace package 可选但功能受限。

Python 的 __init__.py 文件不是“必须的”,但它的存在直接决定模块能否被导入
在 Python 3.3+ 中,__init__.py 不再是包(package)的强制标识——没有它,普通目录也能通过 namespace package 机制被识别。但只要你用 import 做常规导入(比如 from mypkg import stuff),缺了它,大多数情况会报 ModuleNotFoundError: No module named 'mypkg'。
它本质是一个“激活开关”:一旦存在(哪怕为空),Python 就把所在目录当作传统包处理,支持相对导入、自动执行初始化逻辑、控制 from mypkg import * 的行为。
- 空文件就足够让
import mypkg成功 —— 这是最小有效形态 - 如果删掉它,又没做
pyproject.toml配置或安装为 namespace package,那么import会直接失败 - 注意:IDE 或测试工具(如 pytest)有时会“假装”能识别无
__init__.py的目录,但这不代表运行时可靠
__init__.py 里写代码,会在第一次 import 包时自动执行
它就像包的“构造函数”,所有顶层语句都会在首次导入该包时同步运行一次。这不是装饰器或钩子,是解释器硬编码的行为。
典型用途包括:预加载子模块、设置包级变量、触发副作用(如注册插件、检查依赖)。
立即学习“Python免费学习笔记(深入)”;
- 常见错误:在里面调用耗时操作(如读大文件、连数据库),会导致所有导入都变慢,且无法 lazy 加载
- 不要在其中做条件判断后抛异常(如
if not HAS_SOME_LIB: raise ImportError),这会让整个包不可用,而不是只让相关功能失效 - 示例:让
from mypkg import utils可用,可在__init__.py里写from . import utils—— 这叫“展平导入接口”
__all__ 在 __init__.py 中只影响 from xxx import *,对常规 import 无效
很多人误以为设了 __all__ = ['A', 'B'] 就能限制别人 import mypkg 后访问 my_pkg.C,其实不能。它只约束星号导入的符号列表。
真实作用是明确对外 API 边界,配合文档和类型检查工具(如 mypy)更有效。
- 如果
__init__.py是空的,from mypkg import *默认不导入任何东西(除非子模块自己定义了__all__) - 若想隐藏内部模块,别把它 import 进
__init__.py,而不是靠__all__挡住 —— 后者只是“礼貌性提示” -
__all__必须是字符串列表,写成__all__ = A, B(漏方括号)会导致SyntaxError
Python 3.3+ 的 namespace package 让 __init__.py 可选,但行为差异很大
当多个分散路径(如 /path1/mypkg/ 和 /path2/mypkg/)都不含 __init__.py,且都被加进 sys.path,Python 会把它们合并成一个逻辑包 —— 这就是 namespace package。
但它不支持相对导入、不能定义 __all__、也不能在包层级运行初始化代码。
- 如果你看到项目里有同名包分布在不同位置(比如插件系统),很可能用了这个机制
-
pip install -e .安装时默认创建的是 regular package(带__init__.py),不是 namespace package - 要显式声明 namespace package,得在
pyproject.toml里写[project.namespace-packages] = ["mypkg"],否则解释器不会合并
__init__.py 后看似还能跑通某些导入,其实是测试环境或 IDE 的缓存/补丁在起作用,上线部署时大概率崩。










