__init__.py 是包的接口声明文件,用于控制对外暴露的api、初始化轻量配置,并确保工具链正常工作;它必须存在且应显式精简导出,而非填充实现或执行耗时操作。

直接说结论:__init__.py 不是用来“填满”的,而是用来“收口”和“声明”的——它决定别人怎么用你的包,而不是你怎么组织文件。
__init__.py 必须存在,哪怕它是空的
Python 3.3+ 虽然支持隐式命名空间包,但空的 __init__.py 仍是事实标准。没有它,IDE 可能无法正确识别包路径,pytest 可能跳过测试目录,CI 构建时也可能因导入失败而中断。
- 所有子包目录(如
src/api/v1/、src/core/utils/)都必须有__init__.py,哪怕内容为空 - 不建议依赖隐式包——团队协作中,显式即可靠
- Git 中保留空文件:用
touch src/core/__init__.py,别删
用 __all__ 显式控制 from package import *
不声明 __all__ 时,from mypkg import * 会把 __init__.py 里所有非下划线开头的名称全拉进来,包括临时变量、调试函数、未完成的重构残留——这是大型项目里最隐蔽的命名污染源。
-
__all__应该只包含你**明确承诺对外公开**的接口,比如["Client", "Config", "load_config"] - 避免写
from .submodule import *再配__all__——这会让实际导出内容脱离控制,难以审计 - 推荐写法:
from .config import Config, load_config,然后__all__ = ["Config", "load_config"]
在 __init__.py 里做初始化,但别做耗时操作
包首次被导入时,__init__.py 全局代码会执行一次。这适合设常量、配日志、注册插件,但绝不适合连接数据库、加载大模型或读取 GB 级配置文件。
- 常见安全初始化:
logging.getLogger(__name__).addHandler(...)、VERSION = "2.4.0"、__version__ = VERSION - 禁止在
__init__.py里调用requests.get()或open("huge.yaml")—— 这会导致任何导入该包的单元测试变慢甚至失败 - 若需动态初始化(如自动发现子模块),用惰性函数封装,而非直接执行:
def get_drivers(): ...,不在顶层调用
通过 __init__.py 暴露统一入口,但别掩盖真实结构
用户从 myproject.api 导入 create_user,不等于这个函数必须物理存在于 api/__init__.py。合理做法是让它留在 api/endpoints/user.py,再由 api/__init__.py 显式导入并暴露。
- 好处:IDE 能跳转到真实实现;git blame 清晰;重构时只需改一处导入
- 坏做法:
from .endpoints.user import *—— 会把user.py里所有公有名都拖进来,破坏封装 - 更清晰的写法:
from .endpoints.user import create_user, delete_user,再列进__all__
真正难的不是写 __init__.py,而是每次新增一个模块时,想清楚:“这个东西,我打算让谁、在什么场景下、以什么方式来用它?”——__init__.py 就是那个回答问题的地方。写得太满,别人不敢动;写得太空,别人不知道怎么开始。平衡点就在“最小必要暴露”上。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











