第 10 章 · 实战:数据处理与脚本

收官实战:把前九章的零件(容器、文件、异常、生成器、装饰器、argparse)组装成 Python 的看家本领——数据处理与自动化脚本。三个完整项目:CSV 数据处理(标准库)、网络请求与 API(第三方库的第一课)、以及一个带命令行界面的成品工具。学完这章,「Python 能帮我干什么」就有具体答案了。

10.1 项目一:CSV 数据处理(标准库)

任务:一份 students.csv(姓名,科目,分数),统计每人平均分并输出排名。CSV 模块处理「带逗号的字段」这类手写 split 会翻车的格式:

import csv
from collections import defaultdict
from pathlib import Path

def load_scores(path: Path) -> dict[str, list[float]]:
    scores = defaultdict(list)                     # 第 4 章惯用法
    with open(path, encoding="utf-8") as f:        # 第 5 章 with + utf-8
        for row in csv.DictReader(f):              # 每行是字典!
            scores[row["name"]].append(float(row["score"]))
    return scores

def rank(scores: dict[str, list[float]]) -> list[tuple]:
    avg = {name: sum(v) / len(v) for name, v in scores.items()}   # 推导式
    return sorted(avg.items(), key=lambda kv: kv[1], reverse=True)  # 第 8 章 key=

def report(rows, out="rank.txt"):
    with open(out, "w", encoding="utf-8") as f:
        f.write("名次 姓名 平均分\n")
        for i, (name, avg) in enumerate(rows, 1):
            f.write(f"{i:>3} {name:<8} {avg:.1f}\n")

if __name__ == "__main__":
    report(rank(load_scores(Path("students.csv"))))

函数分工(load/rank/report 各管一段)+ 类型注解 + pathlib + defaultdict + 推导式,前九章的所有招式在同一屏会师。Excel 文件换 openpyxl(pip 装),API 同款;大数据量把列表换成生成器管道(第 8 章),GB 级文件内存恒定。

10.2 项目二:网络请求与 API(requests)

第三方库的第一课。requests(事实标准,pip install requests)把 HTTP 变成人话(对比 Java 课第 13 章的 Socket 和第 9 章 API):

import requests

# GET:查(以公共 API 为例)
r = requests.get("https://api.github.com/repos/Sherfiter/Wiki", timeout=10)
r.raise_for_status()                       # 非 2xx 直接抛异常(第 5 章)
data = r.json()                            # JSON → 字典(第 7 章 json 模块)
print(data["stargazers_count"], data["language"])

# 带参数与请求头
r = requests.get("https://api.github.com/search/repositories",
                 params={"q": "python", "sort": "stars"},
                 headers={"User-Agent": "learn-python"},
                 timeout=10)

# POST:提交 JSON
r = requests.post(url, json={"name": "test"}, timeout=10)

纪律三条:timeout 必设(默认无超时 = 挂死风险)、raise_for_status 必调(404 不抛异常,不查就静默拿错误页)、密钥不进代码(环境变量 os.environ 或 .env,软件工程课第 9 章)。JSON 的读写(json.load/dump 与文件结合)是「配置文件」的 Python 惯用形态。

爬虫一瞥:requests + 正则/BeautifulSoup 抓静态页面即可起步;纪律:看 robots.txt、限速(time.sleep)、只抓公开数据。动态渲染页面(第 8 章 Web 前端的 SPA)要上 Selenium/Playwright(驱动真浏览器),那是另一门课的入口。

10.3 项目三:成品工具(argparse 命令行)

把 10.1 升级成正式工具:命令行参数、退出码、错误处理、日志:

import argparse, sys, logging

logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s")

def main(argv=None) -> int:               # 返回退出码(0 成功)
    parser = argparse.ArgumentParser(description="学生成绩排名工具")
    parser.add_argument("csv", help="输入 CSV 文件")
    parser.add_argument("-o", "--out", default="rank.txt", help="输出文件")
    parser.add_argument("-n", "--top", type=int, default=10, help="只显示前 N 名")
    args = parser.parse_args(argv)

    try:
        rows = rank(load_scores(Path(args.csv)))[: args.top]
    except FileNotFoundError:
        logging.error("文件不存在:%s", args.csv)
        return 1                          # 非 0 = 失败,shell 可感知
    report(rows, args.out)
    logging.info("已生成 %s", args.out)
    return 0

if __name__ == "__main__":
    sys.exit(main())
$ python ranker.py students.csv -o out.txt -n 5
INFO 已生成 out.txt
$ python ranker.py nofile.csv ; echo $?
ERROR 文件不存在:nofile.csv
1

argparse 自动生成 --help、自动校验类型;sys.exit 的退出码让脚本可以被 shell 组合(unix 管道思想)。「工具感」的三件套:argparse(参数)+ logging(替代 print,可分级可关)+ 返回码(可组合)——这是脚本与玩具的分界线(软件工程课第 10 章「可观测性」的个人版)。

10.4 进阶地图:接下来学什么

按方向选下一站,每条都站在本课地基上:

  • 数据分析:pandas(DataFrame = 会算术的字典套列表,numpy 底座)→ matplotlib 画图;数据科学的主线(配 Jupyter,第 1 章);
  • Web 后端:Flask(小而美,几行起服务)或 FastAPI(现代,自动文档 + 类型校验,第 7 章注解的变现);数据库课的 SQL 在此落地(SQLite + sqlite3 零配置);
  • 自动化运维:subprocess + pathlib + 定时任务,「把重复劳动自动化」是 Python 的初心;
  • AI/数据科学:numpy → PyTorch(AI 课第 7 章的实验平台,本站 AI 课代码全用 Python 写)。

10.5 Pythonic 十诫(全课总结)

  1. 可变默认参数是地雷(第 3 章);2. with 管资源(第 5 章);3. 推导式优于循环拼列表(第 3 章);4. 生成器处理大数据(第 8 章);5. EAFP 优于先检查(第 5 章);6. 先查标准库再造轮子(第 7 章);7. 鸭子类型 + 类型注解(第 6、7 章);8. 装饰器剥离横切关注点(第 9 章);9. print 换 logging、脚本配 argparse(本章);10. 「代码是写给人读的,顺便让机器执行」(Python 之禅)。

常见错误

  • DictReader 的字段名对不上。表头有 BOM/空格,row["name"] 变 None;encoding="utf-8-sig" 与 strip 处理。
  • requests 不设 timeout。默认永久等待,脚本卡死在半路;每请求必带 timeout。
  • 把密码/密钥写进代码推上 GitHub(软件工程课第 9 章事故清单);环境变量或配置文件(gitignore)。
  • 一次性脚本不做函数拆分。300 行顶层代码没人敢改;哪怕 50 行也按 load/process/report 分函数,未来会感谢你。

练习

  1. 综合:做一个「班级成绩分析器」:读 CSV → 每科统计(最高/最低/平均)→ 每人加权总分 → 输出排名 CSV;全部函数化 + 类型注解 + argparse,支持 --subject 过滤科目。
  2. 动手:GitHub 情报工具:输入用户名,用 requests 抓该用户全部公开仓库(star 数、语言),输出 Markdown 表格;加 @retry 装饰器(第 9 章)和分页处理。
  3. 动手:文件整理器:扫描「下载」目录,按扩展名自动归类(图片/文档/压缩包)到子目录;--dry-run 参数先预览不执行(运维工具的安全惯例)。
  4. 思考:复盘全课程:Python 的「快」(开发效率)来自哪些设计(动态类型、REPL、生态、缩进语法),它的「慢」(运行性能)又来自同样的设计?第 1 章计组课的「抽象税」在这里给出最终案例——写出你自己的「Python 哲学」一段话,作为这门课的结业感想。