Python
Python 模块与包知识总结
整理 Python 模块与包的概念、模块导入与缓存机制、常见导入方式,以及 __init__.py 的初始化与导出作用,并总结循环导入的原因和处理方法。
Python 模块与包知识总结
一、模块(Module)
1.1 什么是模块
- 模块就是一个
.py 文件,包含函数、类、变量等代码。
- 作用:把代码组织成可复用的单元,便于维护和调用。
1.2 模块的优点
- 极大提高程序员的效率。
- 多个功能由统一的接口进行调用。
- 避免命名冲突,代码结构清晰。
1.3 模块的内置属性 __name__
- 当模块被直接运行时,
__name__ == "__main__"。
- 当模块被导入时,
__name__ 等于模块名。
1 2 3
| if __name__ == "__main__": print("测试代码")
|
1.4 模块的导入机制
执行 import foo 时,底层发生以下事情:
- 查找模块:找到
foo 模块所在的 .py 文件。
- 执行模块代码:从上至下依次加载
foo 模块中的每一行代码(如果有打印信息,也会随加载而运行)。
- 创建命名空间:加载完成后,将
foo 模块内的名称空间加载到自己的文件中。
- 缓存机制:同一个模块只会被加载一次(缓存到
sys.modules)。
二、模块的导入方法
2.1 导入整个模块
1 2
| import math print(math.sqrt(16))
|
2.2 模块起别名
1 2
| import numpy as np arr = np.array([1, 2, 3])
|
2.3 一次导入多个模块
2.4 导入模块中的成员
1 2
| from math import sqrt, pi print(sqrt(25))
|
2.5 成员起别名
1 2
| from math import sqrt as square_root print(square_root(9))
|
2.6 导入所有成员(不推荐)
1 2
| from math import * print(sqrt(16))
|
- 缺点:无法知道导入了哪些名字,容易覆盖同名变量,可读性差。
- 模块可通过
__all__ = ["a", "b"] 控制 import * 的导出内容。
2.7 导入包
1 2 3 4 5 6 7 8 9 10 11
| import mypackage.math_utils mypackage.math_utils.add(1, 2)
from mypackage import math_utils math_utils.add(1, 2)
from mypackage.math_utils import add add(1, 2)
|
2.8 动态导入
1 2 3
| import importlib math_module = importlib.import_module("math") print(math_module.sqrt(16))
|
2.9 条件导入
1 2 3 4
| try: import ujson as json except ImportError: import json
|
- 应用场景:兼容不同 Python 版本、可选依赖。
2.10 相对导入(包内部使用)
1 2 3
| from . import c from .. import a from ..a import func_x
|
- 只能在包内使用,不能在顶层脚本直接运行。
. 表示当前目录,.. 表示上一级。
2.11 导入方法对比
| 导入方式 |
示例 |
使用方式 |
| 导入整个模块 |
import math |
math.sqrt() |
| 模块起别名 |
import numpy as np |
np.array() |
| 导入成员 |
from math import sqrt |
sqrt() |
| 成员起别名 |
from math import sqrt as s |
s() |
| 导入所有 |
from math import * |
sqrt() |
| 导入包内模块 |
from pkg import mod |
mod.func() |
| 动态导入 |
importlib.import_module() |
运行时决定 |
| 条件导入 |
try: import ujson |
兼容环境 |
| 相对导入 |
from . import a |
包内使用 |
2.12 导入顺序规范(PEP 8)
1 2 3 4 5 6 7 8 9 10
| import os import sys
import numpy as np import pandas as pd
from mypackage import utils
|
三、包(Package)
3.1 什么是包
- 包是一个包含多个模块的目录,用于组织更大的项目。
- Python 3.3 之前必须在包内有
__init__.py;3.3 之后可以省略,但推荐保留。
3.2 目录结构示例
1 2 3 4 5 6
| myproject/ ├── main.py └── mypackage/ ├── __init__.py ├── math_utils.py └── string_utils.py
|
3.3 包与模块对比
| 对比项 |
模块 Module |
包 Package |
| 本质 |
一个 .py 文件 |
一个目录(含多个模块) |
| 作用 |
组织代码为可复用单元 |
组织多个模块为更大项目 |
| 导入 |
import module |
import package.module |
是否需 __init__.py |
不需要 |
3.3+ 可省略,建议保留 |
3.4 嵌套包(子包)
1 2 3 4 5 6 7
| myproject/ ├── main.py └── pkg/ ├── __init__.py └── subpkg/ ├── __init__.py └── tools.py
|
1 2 3 4 5 6
| from pkg.subpkg import tools tools.hello()
from pkg.subpkg.tools import hello hello()
|
四、__init__.py 详解
4.1 __init__.py 是什么
- 一个特殊的文件(可以是空文件,也可以写代码)。
- 作用:标记文件夹是一个包。
- Python 3.3+ 可省略,但推荐保留。
4.2 三大作用
- 标记文件夹为包:让 Python 识别该目录为包。
- 包初始化代码:
import 包名 时,__init__.py 会自动执行一次。
- 包内注册模块 / 简化导入:把包内常用功能提升到包级别。
4.3 包内注册示例
目录结构:
1 2 3 4
| mypackage/ ├── __init__.py ├── math_utils.py # 定义了 add() └── text_utils.py # 定义了 upper()
|
1 2 3 4 5
| from .math_utils import add from .text_utils import upper
__all__ = ["add", "upper"]
|
1 2 3 4
| from mypackage import add, upper print(add(1, 2)) print(upper("hello"))
|
4.4 常见写法总结
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
from .math_utils import add, sub from .text_utils import upper, lower
__version__ = "1.0.0" __author__ = "作者名"
__all__ = ["add", "sub", "upper", "lower"]
print("包已加载") CONFIG = {"debug": True}
|
4.5 概念区分
| 概念 |
说明 |
| 详细导入 |
从模块路径直接导入,如 from pkg.math_utils import add |
| 包内注册 |
在 __init__.py 中把子模块的成员提升到包级别,如 from .math_utils import add |
五、循环导入问题
5.1 什么是循环导入
两个或多个模块互相导入对方,形成”你导入我,我导入你”的死循环。
5.2 报错信息
AttributeError: partially initialized module ...
ImportError
5.3 出错原因
- Python 导入模块时是”边执行边加载”,互相等待会拿到半成品模块。
- 使用
from b import func_b 时更容易触发。
5.4 解决方法
方法 1:把 import 放到函数内部(最常用)
1 2 3 4
| def func_a(): import b b.func_b()
|
方法 2:把 import 放到文件末尾
1 2 3 4 5
| def func_a(): b.func_b()
import b
|
方法 3:把共享部分抽到第三个模块(最优雅)
1 2
| 原结构:a.py ←→ b.py 新结构:a.py → c.py ← b.py
|
方法 4:用 import b 代替 from b import xxx
1 2 3 4 5 6
| from b import func_b
import b b.func_b()
|
方法 5:只在类型注解中使用 TYPE_CHECKING
1 2 3 4 5 6 7
| from typing import TYPE_CHECKING
if TYPE_CHECKING: from b import B
def process(obj: "B") -> None: ...
|
5.5 方法对比
| 方案 |
适用场景 |
| 函数内 import |
临时解决、快速修复 |
| import 放末尾 |
快速绕过 |
| 抽到第三个模块 |
代码结构可以重构 |
import 代替 from...import |
微调即可 |
TYPE_CHECKING |
仅类型注解用 |