函数与模块
函数与模块 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。
学习目标
这一课把 P02 那种「一大段脚本」拆成函数和模块:解析、校验、统计各管各的,每个函数都能单独调用验证,并且导入模块时不会带出一堆副作用。
问题场景
P02 的清洗统计写在一个文件里还好,现在加需求:既要支持本站导出的 JSON 记录,又要支持手写的 CSV 记录,还得在数据非法时给出提示。全塞一个函数,改一处崩三处,想测「只测统计」都做不到——因为解析代码就长在统计旁边。
前端的直觉在这里照样适用:一个组件只做一件事,工具函数抽出去,模块按职责分文件。
核心解释
拆的标准不是行数,是每个函数一条能一句话说清的职责,并且输入输出可预期:
# parsing.py —— 只负责把原始数据变成标准记录
def parse_record(raw: dict) -> dict:
"""把任意来源的一条记录归一化,缺字段显式报错。"""
course = raw["course"]
minutes = raw.get("minutes")
return {"course": course.upper(), "minutes": minutes or 0, "done": bool(raw.get("done"))}
# stats.py —— 只负责统计,输入必须是已清洗数据
def count_by_course(records: list) -> dict:
counts: dict = {}
for r in records:
counts[r["course"]] = counts.get(r["course"], 0) + 1
return counts
模块级的两条纪律比拆函数本身更重要:
- 入口逻辑必须放在
if __name__ == "__main__":守卫里。模块被import时整个文件会从头执行一遍——不守卫,import stats就会触发统计和打印。FastAPI 项目里这条同样成立:模块导入时应该只定义路由和函数,不连数据库、不读文件。 - 依赖方向要单向。
main.py导入parsing和stats,后两者不回头 importmain。一旦形成循环导入,说明职责切分错了。
用法上看效果:
# main.py
from parsing import parse_record
from stats import count_by_course
RAW = [{"course": "p07", "minutes": 25}]
if __name__ == "__main__":
clean = [parse_record(r) for r in RAW]
print(count_by_course(clean)) # {'P07': 1}
示例拆解
验证「可测试」和「无副作用」两件事:
python main.py # {'P07': 1},入口逻辑只在直接运行时执行
python -c "import parsing; print(parsing.parse_record({'course':'x','minutes':None}))"
# {'course': 'X', 'minutes': 0, 'done': False}——单函数验证通过
第二条命令就是最朴素的「单元测试」:不跑主流程,只调用一个函数对照输出。拆完之后,parse_record 负责归一化、count_by_course 负责统计、main 负责组装,三个文件的职责边界一眼可见。到 P19 你会用 APIRouter 把这个思路搬到接口层:路由文件管 HTTP,业务模块管逻辑,结构完全同构。
动手任务
- 把 P02 的
total_minutes挪进stats.py,在main.py里导入使用,确认运行结果不变。 - 给
parse_record补一条校验:course 缺失时抛ValueError("记录缺少 course"),并写两行调用验证正常/异常两种输入。 - 故意在
stats.py顶层加一行print("stats 被导入"),观察python main.py输出几次,再把它移进函数体,理解「导入即执行」。
常见错误
- import 后行为怪异:模块顶层有读文件、连库、input 等语句,导入即执行;全部挪进函数或
__main__守卫。 - 一个函数既解析又统计又打印:职责混在一起没法单独测;打印交给调用方或
main。 - 文件名和标准库冲突:给自己的模块起名
json.py、stats撞上第三方包,导入顺序一变就出错;模块名避开常见库。 - 循环导入:A import B、B import A,一跑就
ImportError;把共享的东西抽到第三个模块。
检查与复盘
自查清单:
- 每个函数能用一句话说清职责,且能脱离主程序单独调用
python main.py和import stats的行为差异说得清- 新增功能时先想清楚它该落在哪个文件,而不是顺手塞 main
参考资料
- Python 官方教程:模块 —— import 机制与
__main__守卫 - Python 官方教程:函数定义 —— 参数、返回值与文档字符串
读完这一课了?
标记完成后,工作台会安排接下来的学习。