Python 基础:`__init__.py` 文件是干什么的?
学习笔记:理解 Python 包(package)的入口文件
__init__.py的三个作用,以及 import 时常见踩坑。
前言
在 Python 项目里,你会经常看到名为 __init__.py 的文件。名字里的 __init__ 和类里的构造函数类似,表示「初始化」;前后双下划线是 Python 的魔术命名,不能随意改成别的名字。
例如项目结构:
src/
└── test/
├── __init__.py
├── myinit.py
└── other.py
本文总结 __init__.py 的三个核心作用。
作用一:把目录标记为「包」,支持 from ... import
只有普通文件夹时,Python 不一定能按包的方式导入。在目录下放置 __init__.py 后,该目录会被识别为一个 包(package),其他代码就可以用 from ... import 引用其中的模块。
示例结构:
src/test/
├── __init__.py ← 有它,test 才是包
├── myinit.py
└── other.py
myinit.py:
def sayinit() -> str:
return "init"
在其他文件中引用:
from src.test.myinit import sayinit
print(sayinit()) # 输出: init
要点:
__init__.py可以是空文件,仅用于「声明这是包」- 导入路径:
包名.模块名.对象名,例如src.test.myinit.sayinit - 调用函数要带括号:
sayinit(),写sayinit只会打印函数对象本身
作用二:定义「从包对外暴露什么」
可以在 __init__.py 里把子模块的内容「再导出」,让外部调用更简洁。
src/test/__init__.py:
from .myinit import sayinit
__all__ = ["sayinit"]
外部就可以这样写:
from src.test import sayinit
print(sayinit()) # 输出: init
而不必写完整路径 from src.test.myinit import sayinit。
关于 __all__
__all__ 是一个字符串列表,主要用来控制:
from src.test import * # 只会导入 __all__ 里列出的名字
| 说明 | 细节 |
|---|---|
| 是否必须 | 不是。即使不写 __all__,from src.test import sayinit 仍可用(只要在 __init__.py 里 import 了) |
| 名字必须唯一 | 同一个名字不能出现两次 |
| 典型用途 | 声明包的「公开 API」 |
多个模块里有同名函数怎么办?
如果两个文件里都有 sayinit,不能直接写两个同名导出,后面的会覆盖前面的。
目录结构:
src/test/
├── __init__.py
├── myinit.py → def sayinit(): return "init"
└── other.py → def sayinit(): return "other"
正确写法:用 as 起别名
from .myinit import sayinit as sayinit_from_myinit
from .other import sayinit as sayinit_from_other
__all__ = ["sayinit_from_myinit", "sayinit_from_other"]
使用:
from src.test import sayinit_from_myinit, sayinit_from_other
print(sayinit_from_myinit()) # init
print(sayinit_from_other()) # other
错误写法(不要这样):
from .myinit import sayinit
from .other import sayinit # ❌ 覆盖上一个
__all__ = ["sayinit", "sayinit"] # ❌ 无意义
作用三:包被 import 时自动执行
当其他文件第一次 import 这个包(或包下的子模块)时,Python 会先执行 __init__.py 里的代码,再加载具体模块。
src/test/__init__.py:
print("nihao")
from .myinit import sayinit
__all__ = ["sayinit"]
其他文件:
from src.test import sayinit
控制台会先输出:
nihao
然后才能正常使用 sayinit。
执行顺序(简化)
from src.test import sayinit
│
├─ 1. 执行 src/test/__init__.py (print、from .myinit import ...)
│
└─ 2. 加载 src/test/myinit.py
重要特性
| 特性 | 说明 |
|---|---|
| 只执行一次 | 同一进程内,包首次 import 时执行,之后用缓存,不会重复执行 |
| 副作用要小心 | 不要在 __init__.py 里随意写 print、连数据库等,除非是有意设计 |
| 实际项目用法 | 例如在 src/config/__init__.py 里 load_dotenv(".env.dev"),保证读配置前先加载环境变量 |
常见踩坑
1. 混用两种 import 路径,导致 __init__.py 执行两次
import test # 模块名: test
from src.test.myinit import sayinit # 模块名: src.test
Python 会把 test 和 src.test 当成两个不同模块,于是 __init__.py 里的 print("nihao") 会打印两次。
建议: 同一项目统一用一种风格,例如都用 from src.test import ...。
2. 打印函数忘了加括号
print(sayinit) # <function sayinit at 0x...>
print(sayinit()) # init
3. import test 和 from src.test import 不是一回事
取决于 sys.path 和项目结构,路径写法不一致容易重复加载或找不到模块。生产项目推荐以项目根目录为基准,统一使用 from src.xxx import ...。
和类里的 __init__ 对比
类的 __init__ |
包的 __init__.py |
|
|---|---|---|
| 触发时机 | 创建实例 obj = MyClass() |
第一次 import 包时 |
| 作用 | 初始化一个对象 | 初始化整个包 |
| 能否省略 | 可以(用默认构造) | 可以(空文件),但常用来放包级逻辑 |
小结
__init__.py 的三个作用:
- 标记包:让目录可以被
from ... import引用 - 对外暴露 API:在包级别 re-export,
__all__控制import * - 包初始化:第一次 import 时自动执行(加载配置、创建单例等)
记住三句话:
- 名字必须是
__init__.py,不能改 - 同名导出用
as区分 - 整个进程里通常只执行一次,但不要用两种路径重复 import 同一个包
更多推荐
所有评论(0)