用 Python csv.DictReader 校验表格:区分空值、缺失值和多余列
CSV 常被用来交换用户或商品清单。它看起来只是逗号分隔的文本,但读取时有三个不同的问题:city 留空、整行少了一个字段、整行多了一个字段。若把它们统统当成空字符串,后续数据处理就可能悄悄吞掉格式错误。
下面用 Python 标准库 csv.DictReader 做一个小型校验器。示例要求表头严格为 name,age,city;name 必填,age 是 0~120 的整数,city 可以是空字符串。
完整代码
保存为 csv_people.py:
"""Read a small CSV file with explicit column and value checks."""
from __future__ import annotations
import csv
import sys
from pathlib import Path
def read_people(path: Path) -> list[dict[str, str | int]]:
people: list[dict[str, str | int]] = []
with path.open("r", encoding="utf-8-sig", newline="") as source:
reader = csv.DictReader(source, restkey="__extra__", restval=None)
expected = ["name", "age", "city"]
if reader.fieldnames != expected:
raise ValueError(f"表头应为 {expected},实际为 {reader.fieldnames}")
for row in reader:
line = reader.line_num
if "__extra__" in row:
raise ValueError(f"第 {line} 行有多余的列:{row['__extra__']}")
missing = [key for key in expected if row[key] is None]
if missing:
raise ValueError(f"第 {line} 行缺少列值:{missing}")
if not row["name"].strip():
raise ValueError(f"第 {line} 行 name 不能为空")
try:
age = int(row["age"])
except ValueError as error:
raise ValueError(f"第 {line} 行 age 必须是整数") from error
if not 0 <= age <= 120:
raise ValueError(f"第 {line} 行 age 超出 0~120")
people.append({"name": row["name"], "age": age, "city": row["city"]})
return people
if __name__ == "__main__":
for person in read_people(Path(sys.argv[1])):
print(person)
准备 people.csv:
name,age,city
Alice,28,
Bob,0,Shanghai
运行 python csv_people.py people.csv,得到:
{'name': 'Alice', 'age': 28, 'city': ''}
{'name': 'Bob', 'age': 0, 'city': 'Shanghai'}
Alice 的 city 是空字符串,表示第三列存在,只是没有填写。若把该行改成 Alice,28,DictReader 会用 restval=None 补齐 city;校验器便报“缺少列值”。若改成 Alice,28,,extra,多出来的值会放进 __extra__,也会报错。
为什么这样读取
reader.fieldnames 与预期列表做精确比较,可以同时发现列顺序不对、缺列、多列以及重复表头。如果业务允许列顺序变化,可以比较列名集合,但仍需单独检查重复名称。
newline="" 让 csv 模块自己处理换行,避免带引号的多行字段或不同系统的换行方式被提前改写。encoding="utf-8-sig" 可读取普通 UTF-8 文件,也会去掉文件开头的 UTF-8 BOM;否则首列表头可能变成带 BOM 的 \ufeffname。
reader.line_num 是读完当前记录后的物理行号。普通单行记录中,它就是出错行;如果字段里包含被引号包住的换行,它指向该记录结束的那一行,定位时要留意这一点。
使用边界
CSV 中的值初始都是文本,age 需要明确转换和范围检查。示例把空 city 视为有效;实际业务若要求城市必填,应增加相应校验。这个示例只适合小文件,因为它把所有结果收集到列表;处理大文件时,可以把函数改成生成器,逐条交给后续流程。
- 点赞
- 收藏
- 关注作者
评论(0)