Python 读写 CSV:逗号、引号和换行为什么不能自己拼
把几列数据导出成 CSV,最容易想到的是 ",".join(row);读回来时再调用 split(",")。如果字段只包含简单英文,这种方式看起来能够工作。但真实数据中,备注可能包含逗号,用户名称可能包含双引号,一条说明也可能分成两行。此时,一个物理行不一定是一条记录,一个逗号也不一定代表字段边界。
CSV 的引号有明确规则。例如,字段包含逗号时通常需要加双引号,字段自身的双引号又需要按规则转义。与其让业务代码维护这些细节,更合适的做法是交给 Python 标准库 csv。
写文件:让 writer 处理字段边界
下面的数据刻意包含逗号、双引号和字段内部的换行。它们都是字段内容,不应变成多余的列或记录。
import csv
from pathlib import Path
fields = ["name", "note"]
rows = [
{"name": "小林", "note": "北京,上海"},
{"name": "小周", "note": '他说 "收到"'},
{"name": "小陈", "note": "第一行\n第二行"},
]
path = Path("report.csv")
with path.open("w", encoding="utf-8-sig", newline="") as f:
writer = csv.DictWriter(f, fieldnames=fields)
writer.writeheader()
writer.writerows(rows)
newline="" 是一个容易忽略的参数。它让 csv 模块负责换行处理,避免文本层的换行转换与 CSV 自身规则互相干扰,在 Windows 上尤其有用。它并不是要求所有字段都不能换行。
这里选择 utf-8-sig,让文件开头带有 UTF-8 BOM,便于部分表格软件识别中文。若下游明确要求不带 BOM 的 UTF-8,就改成 utf-8。编码应按实际消费方约定选择,不能把一种写法当成所有系统的默认答案。
读文件:先核对表头,再检查记录形状
读取时同样使用 csv。utf-8-sig 能去掉文件开头的 BOM,也能读取普通的无 BOM UTF-8 文件,避免首个列名变成带隐藏字符的字符串。
with path.open("r", encoding="utf-8-sig", newline="") as f:
reader = csv.DictReader(f)
if reader.fieldnames != fields:
raise ValueError("CSV 表头与约定不一致")
loaded = []
for record_no, row in enumerate(reader, start=1):
if None in row or any(value is None for value in row.values()):
raise ValueError(f"第 {record_no} 条记录的字段数量不正确")
loaded.append(row)
assert loaded == rows
这个例子要求表头名称和顺序完全一致。若业务允许列顺序不同,可以单独调整检查规则,但仍应验证必需列,不能直接假定输入一定正确。
DictReader 不是业务校验器:默认情况下,多出的字段会归到 None 这个键下,缺失的字段则可能得到 None 值。代码显式检查这两种情况。空字符串则是另一回事:"" 可以表示一个存在但没有填写的单元格,是否允许为空应由业务规则决定。
报错中的序号叫“记录号”,而不是“文件行号”,因为带换行的字段可能跨越多个物理行。若要定位原始文件,还可以结合 reader.line_num 查看解析器已经读取到的物理行位置。
CSV 解析完成后,还有数据类型这一步
CSV 读出的字段通常是字符串。金额、日期、布尔值等仍需要按明确规则转换;不能因为内容看起来像数字,就把所有字段自动转成数字,否则订单号中的前导零可能丢失。外部内容导入表格软件时,公式解释等展示行为也应另行制定策略,CSV 引号本身并不处理这类业务问题。
验证导出逻辑时,可以构造含中文、逗号、引号、内嵌换行和空字符串的数据,执行“写入再读取”,比较还原结果是否一致。这样检查的是字段内容是否保真,比只查看文件里有几个逗号更有意义。
本文由 AI 辅助撰写。示例已在本地验证特殊字符往返、BOM 与无 BOM 读取,以及表头不匹配、缺列和多列的拒绝行为;表格软件的导入表现未在本文中实测。
- 点赞
- 收藏
- 关注作者
评论(0)