>>>PyPathPython 学习站
首页›核心篇›kp-014
核心篇 · Core lv.2 核心 kp-014

模块与包

前置知识:kp-007

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.py

7. 直观类比

sys.path 是图书馆的书架清单:import 就是按清单找书,第一本找到的就算数;sys.modules 是借阅登记表,同一本书只登记一次,再借直接给书号。书放错书架(同名模块不同路径)就会出现"两本一样的书、两个登记条目"。

8. 实例与案例

python
# if __name__ == "__main__" 的经典用法
def main():
    ...

if __name__ == "__main__":
    main()      # 直接运行才执行;被导入时只提供函数

9. 常见误区

  1. 循环导入(circular import) —— a 导入 b、b 顶层又导入 a,谁先执行谁拿到半成品;修复:延迟导入(函数体内 import)、重构公共部分到第三模块。
  2. 把脚本文件名与标准库重名 —— 工作目录有 random.py 时,import random 拿到的是它(sys.path 首位是脚本目录);脚本千万别叫 requests.py、email.py。
  3. 用 import * —— 命名空间被污染、来源不可追溯;只在 REPL 偶尔用。
  4. 以为加个文件夹就是包 —— 没有被正确纳入 sys.path 或缺 __init__.py(3.3+ 命名空间包是例外),导入会失败或语义意外。

10. 自测题

  1. 为什么 if __name__ == "__main__": 能区分"直接运行"与"被导入"?
  2. ModuleNotFoundError 的排查第一步是什么?
  3. 循环导入的两种常见修复法?
参考答案
  1. 解释器为直接运行的脚本把 __name__ 设为 "__main__",导入模块时设为模块名。
  2. python -c "import sys; print(sys.path)" 看目标模块到底在不在搜索路径里(以及优先级)。
  3. 延迟导入(把 import 挪进函数)、抽取共享部分到独立低层模块。

11. 与其他知识点的关系

  • kp-021 虚拟环境:第三方模块从哪来。
  • kp-036 打包:让模块可被 pip install。
  • kp-034 性能:import 成本与延迟导入。

12. 延伸阅读

  • Python 官方教程 §6 Modules
  • PEP 328(相对导入)