📦说明:当你的代码从几十行增长到几百、几千行,良好的组织结构就变得至关重要。本篇深入讲解 Python 的模块(Module)与包(Package)系统,教你如何将代码拆分为逻辑清晰、易于维护、可复用的单元,并掌握
import的各种用法与最佳实践。
你将学会:
- 模块 vs 包的区别
- 如何创建和导入自定义模块
__init__.py的作用与演进- 绝对导入 vs 相对导入
- 避免循环导入的技巧
- 发布自己的包(简要介绍)
1. 为什么需要模块化?
❌ 单文件项目的痛点
my_app.py (2000 行) ├── 数据库操作 ├── 用户认证 ├── 文件处理 ├── Web 路由 └── 工具函数- 难以阅读和维护
- 无法复用部分功能
- 团队协作冲突多
✅ 模块化的优势
my_app/ ├── main.py ├── auth/ ← 用户认证模块 │ ├── __init__.py │ └── login.py ├── db/ ← 数据库模块 │ ├── __init__.py │ └── models.py └── utils/ ← 工具函数 ├── __init__.py └── helpers.py- 高内聚:相关功能放在一起
- 低耦合:模块间依赖清晰
- 可复用:
utils可被其他项目使用 - 易测试:可单独测试每个模块
2. 模块(Module)基础
什么是模块?
- 任何
.py文件都是一个模块 - 模块名 = 文件名(不含
.py)
示例:创建工具模块
# utils/math_tools.pydefadd(a,b):returna+b PI=3.14159导入方式
# 方式1:完整导入importutils.math_toolsprint(utils.math_tools.add(2,3))# 方式2:重命名(推荐长模块名时使用)importutils.math_toolsasmtprint(mt.PI)# 方式3:导入特定成员fromutils.math_toolsimportadd,PIprint(add(1,2))# 方式4:导入所有(不推荐!污染命名空间)fromutils.math_toolsimport*⚠️避免
import *:
- 不清楚导入了什么
- 可能覆盖同名变量
- 破坏代码可读性
3. 包(Package):模块的容器
包的定义
- 包含
__init__.py的目录(Python 3.3+ 可省略,但建议保留) - 可嵌套(子包)
创建包结构
mypackage/ ├── __init__.py ← 标记为包(可为空) ├── core.py └── utils/ ├── __init__.py └── string_ops.py__init__.py的作用
- 标记目录为包(必需,除非使用 PEP 420 命名空间包)
- 控制导入行为(如暴露哪些接口)
- 执行包初始化代码
示例:简化导入
# mypackage/__init__.pyfrom.coreimportmain_functionfrom.utils.string_opsimportclean_text# 外部只需:frommypackageimportmain_function,clean_text💡现代建议:即使为空,也保留
__init__.py以明确意图。
4. 导入机制详解
4.1 绝对导入(Absolute Import)
从项目根目录或 sys.path开始导入。
# 项目根目录下执行frommypackage.coreimportmain_functionfrommypackage.utils.string_opsimportclean_text✅优点:路径清晰,不易出错
✅推荐用于:所有新项目
4.2 相对导入(Relative Import)
从当前模块位置相对导入(仅限包内使用)。
# 在 mypackage/utils/string_ops.py 中from..coreimporthelper_func# 上一级from.formattingimportformat_str# 同级🔑 规则:
.= 当前包..= 上一级包...= 上两级
⚠️限制:
- 不能在主脚本(
__main__)中使用 - 必须通过
-m运行包(见下文)
5. 正确运行包内的脚本
❌ 错误方式(导致相对导入失败)
# 在 mypackage/ 目录下python core.py# 报错:Attempted relative import in non-package✅ 正确方式1:从项目根目录运行
# 项目根目录python -m mypackage.core✅ 正确方式2:在主脚本中使用绝对导入
# main.py (项目根目录)frommypackage.coreimportmain_functionif__name__=="__main__":main_function()然后运行:
python main.py📌黄金法则:
“永远不要直接运行包内部的 .py 文件!”
6.sys.path与模块搜索路径
Python 按顺序在以下位置查找模块:
- 当前脚本目录
PYTHONPATH环境变量- 标准库目录
- 第三方包安装目录(
site-packages)
查看搜索路径
importsysprint(sys.path)临时添加路径(不推荐长期使用)
importsys sys.path.append("/path/to/your/module")importyour_module✅正确做法:通过虚拟环境 + 安装包管理路径
7. 避免循环导入(Circular Import)
问题场景
# file_a.pyfromfile_bimportfunc_bdeffunc_a():returnfunc_b()# file_b.pyfromfile_aimportfunc_a# ← 循环!deffunc_b():return"Hello"运行file_a.py→ImportError
解决方案
方案1:延迟导入(推荐)
# file_b.pydeffunc_b():fromfile_aimportfunc_a# 只在需要时导入returnfunc_a()+" from B"方案2:重构代码
- 将共享功能提取到第三个模块
- 减少模块间直接依赖
方案3:导入移到函数内部
defsome_function():importproblematic_module...8. 发布你的包(简要流程)
当你想分享代码给他人:
步骤1:创建标准结构
mypackage/ ├── setup.py ← 元数据 ├── README.md ├── mypackage/ │ ├── __init__.py │ └── core.py └── tests/步骤2:编写setup.py
fromsetuptoolsimportsetup,find_packages setup(name="mypackage",version="0.1.0",packages=find_packages(),install_requires=[],)步骤3:本地安装(开发模式)
pipinstall-e.# -e 表示可编辑模式📦 安装后,任何地方都可
import mypackage
9. 最佳实践总结
| 场景 | 推荐做法 |
|---|---|
| 导入方式 | 优先绝对导入,包内用相对导入 |
__init__.py | 保留(即使为空),用于控制 API |
| 模块命名 | 小写 + 下划线(data_utils.py) |
| 避免 | import *、循环导入、直接运行包内脚本 |
| 组织原则 | 按功能划分,而非按类型(如不要建classes/,functions/) |
🐍Python 之禅:
“Flat is better than nested.”(扁平优于嵌套)
—— 但合理分层更重要!
10. 实战:重构 To-Do List 项目
回顾第(二十)篇的项目结构:
todo-cli/ ├── todo.py └── todolib/ ├── __init__.py ├── task.py ├── storage.py └── manager.py改进建议:
- 在
todolib/__init__.py中暴露核心接口:from.managerimportTaskManagerfrom.taskimportTask __all__=["TaskManager","Task"] - 主程序简化导入:
fromtodolibimportTaskManager# 而非 from todolib.manager import...
下一步行动
- 将你的工具函数拆分为独立模块
- 尝试创建一个包含多个子包的小型项目
- 阅读知名开源项目(如 requests)的包结构
🧱模块化是软件工程的基石。
从今天起,像建筑师一样设计你的代码结构!
继续构建清晰、可维护、可扩展的 Python 项目!