第 34 课 · 进阶 · 19 分钟

Python CSV 文件

读写表格文本,不必自己按行和逗号拆分。

CSVDictReadercsv.writer引号

会计要一份 CSV,可「拿铁,燕麦」这个名字会撑破所有天真的 split。csv 模块懂引号。DictReader 对第一行做了什么假设?

CSV 看起来简单,直到某个值里出现逗号、引号或换行。csv 模块会处理这些细节,还能把每一行表示成以表头为键的字典。小例子可以用 StringIO,避免留下临时文件。 你还会处理列数不齐、严格写入和字节顺序标记。

把行读成字典

默认情况下,DictReader 把第一行当作字段名。之后每一行都会变成字典。相比记住列的位置,这种方式更易读,列很多时尤其明显。

import csv
from io import StringIO

raw = "name,score\nAda,92\nGrace,88\n"
rows = list(csv.DictReader(StringIO(raw)))
print(rows[0]["name"])

表头名称变成键

用 writer 写入行

csv.writer 一次写入一行,并自动加上行结束符。传入字段列表或元组即可。小型内存示例可以写入 StringIO,再用 getvalue() 查看结果。

import csv
from io import StringIO

buffer = StringIO()
writer = csv.writer(buffer)
writer.writerow(["name", "score"])
writer.writerow(["Ada", 92])
print(buffer.getvalue().strip())

在内存中构建 CSV 文本

给需要的字段加引号

字段中的逗号必须用引号保护。QUOTE_ALL 会给每个字段都加引号,适合另一个工具要求这种格式的场景。读取时 csv 会去掉引号,还原原始值。

import csv
from io import StringIO

buffer = StringIO()
writer = csv.writer(buffer, quoting=csv.QUOTE_ALL)
writer.writerow(["a,b", "c"])
print(buffer.getvalue().strip())

字段内部的逗号

DictReader:缺失列和多余列

DictReader 把表头当键。短行缺少的字段会得到 None;多出来的字段会放进 restkey 指定的键。边界处要认真检查这两种情况,不能假设每一行都正好是预期形状。

import csv
import io

text = "name,price\nlatte,4.5\ntea\nmocha,5.0,note\n"
reader = csv.DictReader(io.StringIO(text), restkey="extra")
for row in reader:
    print(row["name"], row.get("price"), row.get("extra"))

有表头不代表每行都完整

DictWriter:固定列序,严格拒绝多余字段

fieldnames 固定列顺序。extrasaction='raise' 会把意外字段变成错误,而不是悄悄丢掉。固定的 lineterminator 让输出可复现,也能避免 Windows 上出现双换行。

import csv
import io

out = io.StringIO(newline="")
fields = ["name", "price"]
writer = csv.DictWriter(
    out, fieldnames=fields, extrasaction="raise", lineterminator="\n"
)
writer.writeheader()
writer.writerow({"name": "latte", "price": 4.5})
try:
    writer.writerow({"name": "tea", "price": 3.0, "note": "hot"})
except ValueError as error:
    print(type(error).__name__, error)
print(repr(out.getvalue()))

多一列就明确报错

在边界处理 BOM 和换行

表格软件导出的 CSV 经常带 UTF-8 字节顺序标记。用 utf-8-sig 解码,第一个表头才不会变成 \ufeffname。文本流用 newline='' 打开,让 csv 模块而不是操作系统决定行尾。

import csv
import io

raw = b"\xef\xbb\xbfname,price\r\nlatte,4.5\r\n"
text = raw.decode("utf-8-sig")
reader = csv.DictReader(io.StringIO(text, newline=""))
row = next(reader)
print(row["name"], row["price"])

out = io.StringIO(newline="")
writer = csv.writer(out, lineterminator="\n")
writer.writerow(["tea", 3.0])
print(repr(out.getvalue()))

先解码 BOM,再解析

本课要点

  • 使用 csv.reader 或 csv.DictReader,不要自己按逗号拆分。
  • DictReader 默认把第一行当作表头。
  • writerow 写入一行并添加行结束符。
  • 引号可以保护字段内的逗号、引号和换行。
  • 小型内存示例可以使用 StringIO。
  • 检查行列形状;写入时拒绝多余字段;用 utf-8-sig 和 newline='' 控制边界。

常见错误

  • 字段包含逗号时仍然使用 line.split(",")。
  • 忘记 DictReader 读出的值默认是字符串,需要时得转换。
  • 在会转换行结束符的平台上写 CSV 文件时,忘记 newline=""。
  • 打开 CSV 文本时不用 newline='',然后把空行怪到 csv 头上。
轮到你

用 DictReader 解析 'name,score\nAda,92\n',再打印 Ada 的分数。

动手试试

改改变量、先预测输出,再运行代码。第一次点击“运行代码”时会下载 Python 运行时,可能需要几秒钟。

浏览器内 Python本地运行 · 无需账号
首次运行会从 CDN 下载 Pyodide。修改后点击“运行代码”。

练习本课

12 道题,作答后立即看到解释。

查看 12 道题答案开始练习
← 枚举日志 →