核心篇 · Core
lv.2 核心
kp-014
模块与包
1. 一句话定义
.py 文件即模块(module),含 __init__.py 的目录即包(package);import 按 sys.path 查找并执行一次模块顶层代码,把结果缓存进 sys.modules。
2. 为什么重要
从"单文件脚本"到"工程"的第一步就是模块化。理解 import 的查找与执行机制,才能诊断 ModuleNotFoundError、循环导入、双胞胎导入(m vs pkg.m)等真实问题。
3. 前置知识
kp-007(作用域——模块顶层就是全局作用域)。
4. 核心概念
- import 形式:
import a.b、from a.b import c、from a import *(禁用)、from . import x(相对导入)。 __name__:模块被导入时是模块名;被直接运行时是"__main__"——脚本入口判断的原理。__init__.py:包的初始化代码;控制from pkg import *的导出面(__all__),并可聚合导出公共 API。- sys.path:查找顺序 = 脚本所在目录(或 cwd)→
PYTHONPATH→ 安装的包目录。
5. 原理与机制
python
# pkg/__init__.py
from .models import Book # 把子模块 API 提升到包级
__all__ = ["Book"]
# main.py
from pkg import Book # 相当于 from pkg.models import Book
# 模块被执行两次的坑:同一个模块以两种路径导入会得到两个对象
import settings # sys.modules['settings']
import pkg.settings # sys.modules['pkg.settings'] —— 不同的缓存条目!导入即执行:import 触发模块顶层代码执行一次,之后再次 import 只是从 sys.modules 取缓存——这就是"模块级变量天然单例"的原理。
6. 关键事实(模型/图示)
text
import x
1. x 已在 sys.modules? → 直接用
2. 沿 sys.path 找 x.py / x/__init__.py
3. 找到 → 编译执行顶层 → 存入 sys.modules → 绑定名字标准项目布局(src 布局,3.11+ 官方推荐):
text
project/
├── pyproject.toml
└── src/
└── mylib/
├── __init__.py
├── core.py
└── utils.py7. 直观类比
sys.path 是图书馆的书架清单:import 就是按清单找书,第一本找到的就算数;sys.modules 是借阅登记表,同一本书只登记一次,再借直接给书号。书放错书架(同名模块不同路径)就会出现"两本一样的书、两个登记条目"。
8. 实例与案例
python
# if __name__ == "__main__" 的经典用法
def main():
...
if __name__ == "__main__":
main() # 直接运行才执行;被导入时只提供函数9. 常见误区
- 循环导入(circular import) —— a 导入 b、b 顶层又导入 a,谁先执行谁拿到半成品;修复:延迟导入(函数体内 import)、重构公共部分到第三模块。
- 把脚本文件名与标准库重名 —— 工作目录有
random.py时,import random拿到的是它(sys.path 首位是脚本目录);脚本千万别叫requests.py、email.py。 - 用
import *—— 命名空间被污染、来源不可追溯;只在 REPL 偶尔用。 - 以为加个文件夹就是包 —— 没有被正确纳入 sys.path 或缺
__init__.py(3.3+ 命名空间包是例外),导入会失败或语义意外。
10. 自测题
- 为什么
if __name__ == "__main__":能区分"直接运行"与"被导入"? ModuleNotFoundError的排查第一步是什么?- 循环导入的两种常见修复法?
参考答案
- 解释器为直接运行的脚本把
__name__设为"__main__",导入模块时设为模块名。 python -c "import sys; print(sys.path)"看目标模块到底在不在搜索路径里(以及优先级)。- 延迟导入(把 import 挪进函数)、抽取共享部分到独立低层模块。
11. 与其他知识点的关系
12. 延伸阅读
- Python 官方教程 §6 Modules
- PEP 328(相对导入)