本篇是《Python 从入门到初级》第 20 / 20 篇。示例使用 Python 3.12 兼容语法,完整目录结构与数据见配套源码包。
最后一篇把前面学到的文件、CSV/JSON、函数、模块、异常、命令行和测试组合起来,交付一个别人解压后也能使用的订单报表项目。

1. 先写项目交付标准
项目接收一份订单 CSV,验证字段,统计全部订单数、已支付数量和分类金额,输出 JSON。输入金额单位固定为非负整数分,状态固定为 paid、pending、cancelled。
使用者可以查看帮助、预览结果,选择新输出文件;错误时有原因和非零退出码;已有报告不被静默覆盖;测试可以重复运行。
它不承担付款对账、退款、税费或币种换算,也没有并发写入和大规模流式处理。本项目交付的就是这一组明确能力。
2. 目录中分别放什么
order-report/
├── report_cli.py
├── ops_tools/
│ ├── __init__.py
│ └── orders.py
├── data/orders.csv
├── tests/test_orders.py
└── README-project.md
report_cli.py 作为入口,orders.py 实现读取、验证、汇总和输出,data 放教学样本,tests 保存验收预期。完整文件都在源码包中,分别来自第 13、18、19 篇。
这次不再重新复制一套统计逻辑,而是保持已有模块的复用关系。改规则时只需要修改相应职责的位置,再运行测试验证影响。
3. README 要让第一次看到项目的人也能运行
项目附带的 README 内容如下:
# 订单报表工具
需求:读取包含 order_id、category、amount_cents、status 的 CSV,统计全部订单数、已支付订单数、已支付整数分金额及分类金额,输出 UTF-8 JSON。
环境:Python 3.12 或兼容版本,只使用标准库。进入本项目根目录后,使用对应环境的 python 运行。
```text
python -m unittest discover -s tests -v
python report_cli.py --output output/report.json --dry-run
python report_cli.py --output output/report.json
```
示例数据共 5 单,其中 paid 3 单、38300 分。输出文件存在时拒绝覆盖,请选择新文件名。
退出码:成功为 0,文件或业务数据处理失败为 1,命令参数错误为 2。日志写入标准错误,统计 JSON 写入标准输出。
输入规范:金额为非负整数分;订单编号不重复;分类非空;状态为 paid、pending、cancelled 之一。空表允许,但必须保留表头。数据均为教学样本。
不在范围:付款对账、退款、税费、币种换算、海量数据流式处理、并发写入和生产权限管理。
除了启动命令,还写明环境、字段、金额单位、正常基准、退出码和不覆盖规则。一个只写“运行 main.py”的说明,往往省略了接手者最需要的信息。
4. 按验收顺序运行
在项目根目录依次执行:
python -m unittest discover -s tests -v
python report_cli.py --help
python report_cli.py --output output/report.json --dry-run
python report_cli.py --output output/report.json
如果使用完整学习源码目录,会运行 18 项测试;交付的订单子项目只带订单测试,因此是 13 项。干净项目中的报告基准是 5 单、3 笔 paid、38300 分。
再原样生成一次,确认工具拒绝覆盖;换一个新文件名,确认可正常生成。对缺文件和重复编号也各做一次检查,确保失败路径可解释。
5. 用标准库生成一个内容明确的压缩包
在完整学习源码根目录运行 bundle_project.py。它只打包列出的项目文件,不递归收集虚拟环境、缓存或个人配置。
from pathlib import Path
from zipfile import ZIP_DEFLATED, ZipFile
BASE = Path(__file__).resolve().parent
FILES = ["report_cli.py", "ops_tools/__init__.py", "ops_tools/orders.py",
"data/orders.csv", "tests/test_orders.py", "README-project.md"]
def main():
output = BASE / "output" / "order-report-project.zip"
output.parent.mkdir(exist_ok=True)
for name in FILES:
if not (BASE / name).is_file():
raise FileNotFoundError(name)
with ZipFile(output, "x", compression=ZIP_DEFLATED) as archive:
for name in FILES:
archive.write(BASE / name, "order-report/" + name)
print("项目包:", output)
if __name__ == "__main__":
main()
预期生成 output/order-report-project.zip。使用 x 模式意味着同名包已存在时拒绝覆盖,重复练习时可以使用新的输出名称。ZIP 操作机制见 zipfile 文档。
这只是代码交付,不是把 Python 解释器也打进去的独立可执行程序。接手者仍需要匹配的 Python 环境。
6. 在新位置复验,才能发现路径依赖
把包解压到另一个独立目录,进入 order-report,再执行 README 里的测试、预览和生成命令。路径、包结构和样本文件都正确时,结果应保持一致。
如果只在原来的电脑和目录能运行,检查是否遗漏文件、依赖了当前目录、误用了全局安装的包或把绝对路径写进代码。这个练习比只检查“压缩包存在”更接近交付要求。
7. 学完后可以独立尝试什么
用相同结构做文件清单导出、值班记录统计、商品价格对比或内部接口数据同步。先定义输入输出,再拆函数与模块,最后补上边界验证、日志和测试。
如果想继续向运维发展,可以把 HTTP 客户端与退出码接入调度或流水线;向数据方向可以学习更大规模的数据处理库;向 Web 方向可以再学框架和服务部署。
不必把所有方向同时学完。能够把一个具体需求变成可运行、可解释、可验证、别人也能接手的小工具,就是这套从入门到初级教程的实际目标。
上一篇:自动化测试:保护报表与文件整理规则 · 系列目录 · 本篇为系列收尾