第3章 3.5 综合实战:实用工具函数库
🎯 开场:为什么需要自己的工具箱?
上一章我们学会了把代码打包成模块与包,终于不用在每个项目里重复写相同的代码了。但光有「存放代码的地方」还不够,你有没有遇到过这些糟心事:
- 每次要格式化日期,都要百度「Python 日期格式化」
- 一个字符串要去空格、转大小写、截断,代码写得又臭又长
- 读取 CSV 文件,上次写的代码下次拿出来报错
说白了:你在重复造轮子,而且造的还不太圆。
今天这章,我们把「模块与包」的知识真正用起来——手把手教你打造一个自己的工具函数库。学完以后,这些重复劳动将变成一行函数调用。
🧱 基础:什么是工具函数库?
3.5.1 生活类比:工具箱
想象你家的工具箱:
- 钳子、螺丝刀、锤子 → 各种「工具函数」,每种干一件事
- 分类摆放 → 按功能分组(日期工具、字符串工具、文件工具)
- 用的时候直接拿 → 写代码时
import就能用
工具函数库就是代码版的工具箱,把平时常用的功能打包好,随用随取。
3.5.2 为什么要自己写?
有人会说:「Python 不是自带很多库吗?」没错,但:
| 场景 | 自带库 | 自定义工具库 |
|---|---|---|
| 特殊业务逻辑 | 没有 | 按需定制 |
| 组合多个操作 | 要写多行 | 一行搞定 |
| 团队共享 | 无 | 统一风格 |
举个例子:你想把「2024-01-15」这样的日期字符串转成「1月15日」这种中文格式。Python 自带的 datetime 只能帮你转成日期对象,但格式化要查文档、自己写代码。自定义工具库可以这样:
# 你的工具库里一行搞定
date_str = format_date_cn("2024-01-15") # → "1月15日"
这就是工具函数库的价值——把常用操作封装好,下次用直接调。
3.5.3 搭建工具函数库的结构
上一章我们学了包的结构,现在来建一个真正的工具库:
mytools/ # 工具库根目录(包名)
├── __init__.py # 包初始化文件
├── date_utils.py # 日期工具模块
├── str_utils.py # 字符串工具模块
└── file_utils.py # 文件工具模块
注意! __init__.py 的作用是告诉 Python「这个文件夹是个包」,即使它是空的也要有。
3.5.4 第一个工具函数
先从最简单的开始,写一个日期格式化工具。在 date_utils.py 里写:
# date_utils.py
from datetime import datetime
def format_date_cn(date_str):
"""
把 '2024-01-15' 这样的字符串转成 '1月15日'
参数:date_str - 日期字符串,格式必须是 YYYY-MM-DD
返回:中文格式的日期字符串
"""
date_obj = datetime.strptime(date_str, "%Y-%m-%d")
return date_obj.strftime("%m月%d日")
解释:这 3 行代码干了 3 件事:
1. strptime 把字符串解析成日期对象
2. strftime 把日期对象格式化成新字符串
3. 函数封装好,以后直接调用

🔥 实战:3 个递进项目
📦 项目 1:5 分钟搞定「字符串工具箱」(抄改版)
目标:学会用自己写的字符串工具函数
完整代码(可以直接复制运行):
# str_utils.py
def trim_and_upper(text):
"""
去空格 + 转大写,一行搞定
例如:' hello world ' → 'HELLO WORLD'
"""
return text.strip().upper()
def truncate(text, length=20):
"""
截断字符串,超长部分用 ... 代替
例如:'这是一个很长的字符串'(10字)→ '这是一个...'
"""
if len(text) <= length:
return text
return text[:length] + "..."
# 测试一下
if __name__ == "__main__":
text = " Python真好玩 "
print(trim_and_upper(text)) # 输出:PYTHON真好玩
long_text = "这是一段非常非常长的文字"
print(truncate(long_text, 8)) # 输出:这是一段非...
预期输出:
PYTHON真好玩
这是一段非...
一句话解释:函数名就是注释,用 if __name__ == "__main__" 可以直接测试这个文件。
📦 项目 2:10 分钟处理「CSV 数据清洗」(真实场景)
目标:从 CSV 文件读取数据,用工具函数处理后输出
假设你有一个 students.csv 文件,内容如下:
name,score,grade
张三,85,B
李四,92,A
王五,78,C
赵六,88,B
完整代码:
# file_utils.py
import csv
def read_csv(filepath):
"""读取 CSV 文件,返回列表(不含表头)"""
data = []
with open(filepath, "r", encoding="utf-8") as f:
reader = csv.DictReader(f)
for row in reader:
data.append(row)
return data
def filter_by_score(data, min_score):
"""筛选分数大于等于 min_score 的学生"""
result = []
for item in data:
if int(item["score"]) >= min_score:
result.append(item)
return result
def format_student_info(student):
"""格式化学生信息为易读字符串"""
name = student["name"].strip()
score = student["score"]
grade = student["grade"].strip()
return f"{name}:{score}分({grade}等)"
# 主程序
if __name__ == "__main__":
# 读取数据
students = read_csv("students.csv")
# 筛选 80 分以上的
good_students = filter_by_score(students, 80)
# 格式化输出
print("=== 80分以上的学生 ===")
for s in good_students:
print(format_student_info(s))
预期输出:
=== 80分以上的学生 ===
张三:85分(B等)
李四:92分(A等)
赵六:88分(B等)
一句话解释:csv.DictReader 帮我们把每行转成了字典,key 就是表头的名字。

📦 项目 3:15 分钟打造「个人小工具」—— 待办事项管理器
目标:组合前面学的所有知识,做一个有点真实用的小工具
这个工具能做什么:
1. 添加待办事项
2. 查看所有待办
3. 标记完成
4. 数据保存到本地(下次打开还在)
完整代码:
# todo_manager.py
import json
import os
from datetime import datetime
TODO_FILE = "todos.json"
def load_todos():
"""从文件加载待办事项,没有文件就返回空列表"""
if not os.path.exists(TODO_FILE):
return []
with open(TODO_FILE, "r", encoding="utf-8") as f:
return json.load(f)
def save_todos(todos):
"""保存待办事项到文件"""
with open(TODO_FILE, "w", encoding="utf-8") as f:
json.dump(todos, f, ensure_ascii=False, indent=2)
def add_todo(todos, content):
"""添加新的待办事项"""
todo = {
"id": len(todos) + 1,
"content": content,
"done": False,
"created_at": datetime.now().strftime("%Y-%m-%d %H:%M")
}
todos.append(todo)
return todos
def list_todos(todos):
"""显示所有待办事项"""
if not todos:
print("📝 暂无待办事项")
return
for t in todos:
status = "✅" if t["done"] else "⬜"
print(f"{status} [{t['id']}] {t['content']}")
def done_todo(todos, todo_id):
"""标记指定待办为完成状态"""
for t in todos:
if t["id"] == todo_id:
t["done"] = True
print(f"✅ 已完成:{t['content']}")
return True
print(f"❌ 未找到 ID 为 {todo_id} 的待办")
return False
def delete_todo(todos, todo_id):
"""删除指定待办事项"""
for i, t in enumerate(todos):
if t["id"] == todo_id:
deleted = todos.pop(i)
print(f"🗑️ 已删除:{deleted['content']}")
return True
print(f"❌ 未找到 ID 为 {todo_id} 的待办")
return False
# 主程序演示
if __name__ == "__main__":
todos = load_todos()
# 添加几个待办
todos = add_todo(todos, "完成 Python 作业")
todos = add_todo(todos, "买菜做饭")
todos = add_todo(todos, "给妈妈打电话")
save_todos(todos)
# 显示所有
print("\n📋 当前待办:")
list_todos(todos)
# 标记完成
print("\n🎯 标记完成:")
done_todo(todos, 1)
# 再次显示
print("\n📋 更新后待办:")
list_todos(todos)
# 保存
save_todos(todos)
预期输出:
📋 当前待办:
⬜ [1] 完成 Python 作业
⬜ [2] 买菜做饭
⬜ [3] 给妈妈打电话
🎯 标记完成:
✅ 已完成:完成 Python 作业
📋 更新后待办:
⬜ [2] 买菜做饭
⬜ [3] 给妈妈打电话
✅ [1] 完成 Python 作业
一句话解释:json.dump 把 Python 列表转成文本存进文件,下次运行 json.load 又读回来,数据就持久化了。
💪 进阶:常见坑 + 性能小贴士
坑 1:日期格式写错了
# ❌ 错误示例
date_str = "2024-01-15"
date_obj = datetime.strptime(date_str, "%Y/%m/%d") # 格式不匹配!
# ValueError: time data '2024-01-15' does not match format '%Y/%m/%d'
# ✅ 正确示例
date_obj = datetime.strptime(date_str, "%Y-%m-%d") # 格式要一模一样
坑来了:字符串里的 - 要和格式里的 - 对应,不能混用。
坑 2:文件路径写死
# ❌ 错误示例
df = pd.read_csv("students.csv") # 假设文件不在当前目录就报错
# ✅ 正确示例
import os
filepath = os.path.join(os.path.dirname(__file__), "students.csv")
df = pd.read_csv(filepath) # 用包所在目录的相对路径
坑 3:__init__.py 里忘了导入
# ❌ 错误示例(mytools/__init__.py 是空的)
import mytools
mytools.format_date_cn("2024-01-15") # AttributeError: module 'mytools' has no attribute 'format_date_cn'
# ✅ 正确示例(在 __init__.py 里导出)
# from .date_utils import format_date_cn
# from .str_utils import trim_and_upper
坑 4:中文字符串比较
# ❌ 错误示例
text = "Python"
if text == "python": # 大小写不同,比较失败
print("匹配")
# ✅ 正确示例
if text.lower() == "python": # 先转小写再比较
print("匹配")
坑 5:json.dump 中文乱码
# ❌ 错误示例
with open("data.json", "w") as f:
json.dump(data, f) # 中文可能变成 Unicode 转义
# ✅ 正确示例
with open("data.json", "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2) # 中文正常显示
性能小贴士:文件读取优化
如果文件很大,不要一次性读入内存:
# ❌ 低效:全部读入
with open("big_file.csv", "r") as f:
lines = f.readlines() # 1G 文件就占 1G 内存
# ✅ 高效:逐行处理
with open("big_file.csv", "r") as f:
for line in f: # 每次只读一行
process(line)
调试技巧:print 大法
def buggy_function(data):
print(f"🔍 Debug: 收到数据 {data}") # 加一行打印,看函数收到了什么
# ... 中间代码 ...
print(f"🔍 Debug: 处理后结果 {result}") # 看输出对不对
return result
进阶调试:用 pdb(Python 内置调试器):
import pdb
def complicated_function(x):
pdb.set_trace() # 代码执行到这里会暂停,进入交互式调试
result = x * 2 + 1
return result
运行时终端会变成 pdb> 提示符,可以输入 n(下一行)、p 变量名(打印变量)、c(继续运行)。
✏️ 练习题
练习 1(2 分钟):换个日期格式
- 输入:
format_date_cn("2024-12-25") - 预期输出:
"12月25日" - 提示:函数已经写好了,直接调用,改一下输入就行
练习 2(3 分钟):加个判断
- 输入:在
truncate函数里加个判断,如果length < 3就直接返回"..." - 预期输出:
truncate("hello", 2)→"..." - 提示:在函数开头加个
if length < 3: return "..."
练习 3(5 分钟):处理新数据
- 输入:新建一个
grades.csv,内容是subject,score\n数学,95\n语文,88 - 预期输出:打印出「数学:95分」「语文:88分」
- 提示:复用
read_csv和format_student_info的思路,改一下字段名
练习 4(8 分钟):串起两个项目
- 输入:用项目 2 的 CSV 读取 + 项目 1 的字符串工具,读取
students.csv后把姓名去空格转大写输出 - 预期输出:
["张三".strip().upper(), "李四".strip().upper(), ...] - 提示:先读取数据,再用列表推导式处理姓名
练习 5(5 分钟):看懂报错
- 输入:运行以下代码会报什么错?
from datetime import datetime
date_str = "2024/01/15"
result = datetime.strptime(date_str, "%Y-%m-%d")
print(result)
- 预期输出:能说出错误类型和原因
- 提示:注意字符串里的
/和格式里的-
📝 作业:做一个「个人小账本」
需求描述:做一个命令行小工具,记录每天花了多少钱
功能点:
1. 添加支出记录(金额、类别、备注)
2. 查看本月支出汇总
3. 按类别统计支出
加分项:
1. 数据保存到本地 JSON 文件
2. 支持删除某条记录
验收标准:
- 能运行起来
- 添加后能显示
- 关闭再打开数据还在
提交方式:评论区贴代码或 GitHub 链接
📚 总结 + 资源
本文学了 3 件事:
1. 如何组织工具函数库——用模块 + 包的结构,把相关函数放一起
2. 如何封装常用操作——函数封装 + 文档字符串,下次直接调用
3. 如何持久化数据——用 JSON 文件保存,数据不丢失
延伸学习资源:
- Python 官方文档:datetime —— 日期时间处理的权威指南
- Python 官方文档:json —— JSON 操作详解
- 《Python编程:从入门到实践》第 8 章——函数部分讲得很细
互动钩子:
💬 你在写代码时有没有「这段代码我好像写过」的困扰?有没有什么私藏的工具函数想分享?评论区聊聊,帮你优化!

评论(0)