随着项目代码量增长,把所有模块堆在一个文件夹里很快就会变得混乱。Python 通过包(Package)来组织代码——包本质上就是一个包含 init.py 文件的目录,里面可以有子包和模块。理解 init.py 的作用,以及模块间的导入方式,是写出结构清晰、可维护项目的基本功。
init.py 的作用
init.py 是一个包目录下必须存在的文件(Python 3.3 以后允许省略,但强烈不推荐)。它主要有三个职责:
1. 标识目录为 Python 包
只要一个目录里放了 init.py,Python 就会把它识别成一个包。没有这个文件,目录只是一个普通文件夹,无法被 import 识别。
2. 初始化包时执行代码
当包被首次导入时,init.py 会自动执行一次。你可以在这里做:
- 设置包级别的变量或常量。
- 进行一次性初始化操作(比如检查依赖、读取配置)。
- 预先导入子模块,简化外部使用(见后文)。
例如,一个数据分析包 analysis/ 的 init.py:
# analysis/__init__.py
print("analysis 包已加载")
from . import cleaning # 导入子模块
from . import statistics
当执行 import analysis 时,会输出提示,并把 cleaning 和 statistics 作为包的属性,可以直接 analysis.cleaning.xxx 使用。
3. 控制导出接口(all)
在 init.py 中定义 all 列表,可以控制当用户使用 from package import * 时,哪些名字会被导入:
# analysis/__init__.py
__all__ = ['clean', 'analyze']
这能避免不必要的内部名字污染调用方的命名空间。
导入方式:绝对导入 vs 相对导入
包内部模块之间经常需要互相调用,这时就涉及到绝对导入和相对导入的选择。
绝对导入
从项目根目录(通常是 sys.path 中包含的顶级包)开始,写出完整的导入路径。例如项目结构:
project/
├── main.py
└── mypkg/
├── __init__.py
├── module_a.py
└── sub/
├── __init__.py
└── module_b.py
在 module_b.py 中想用 module_a 里的函数,可以用绝对导入:
# mypkg/sub/module_b.py
from mypkg import module_a
优点:路径一目了然,不受当前模块位置影响;在模块移动后容易修改变更点;IDE 支持通常更好。
推荐:绝大多数情况下优先使用绝对导入。
相对导入
使用 . 表示当前包,.. 表示上级包,适用于包内部模块间的调用。例如同样的 module_b.py 可以这样写:
# mypkg/sub/module_b.py
from .. import module_a # 回到 mypkg 包,导入 module_a
from . import module_b_extra # 从当前 sub 包导入同级的 module_b_extra
限制:相对导入只能用在包内部,不能在顶层脚本(main)中使用,否则会抛出 ImportError。
场合:当包结构深层且不想写出长长的绝对路径时,或者希望在重命名顶层包时内部代码不受影响,可以使用相对导入。但注意不要滥用到让代码难以理解。
两者对比
| 维度 | 绝对导入 | 相对导入 |
|------|----------|----------|
| 可读性 | 清晰,直接看出模块位置 | 需要心算层级,嵌套深时易混淆 |
| 重命名包名 | 需要全局修改路径 | 内部不受影响(只要相对层级不变) |
| 灵活性 | 模块移动到不同层级需改路径 | 移动模块时相对关系可能变化 |
| 适用场景 | 包外部导入、顶层脚本 | 包内部、结构稳定的深层调用 |
最佳实践总结
- 始终保留
init.py,即使内容是空的。这能避免许多隐晦的导入错误,并明确告诉协作者“这是一个包”。 - 在
init.py中有选择地导入常用子模块,可以让外部用户更容易使用,比如import mypkg后直接mypkg.do_something(),而不需要层层写mypkg.sub.module.do_something()。 - 优先使用绝对导入,路径清晰,维护友好。只在包内部且希望保持相对独立性时,才谨慎使用相对导入。
- 避免写
from module import *,容易污染命名空间并导致不可追踪的错误。如果要让用户快速访问,在init.py里显式控制。