JSON 与 CSV
程序经常需要把内存中的结构化数据持久化到磁盘,或与其他系统交换数据。JSON 和 CSV 是 Python 标准库直接支持的两种纯文本格式:JSON 擅长表达嵌套的层级结构,是跨语言数据交换的事实标准;CSV 则是扁平表格的最简表示,能被 Excel 直接打开。二者都涉及编码、换行符和类型转换等细节,处理不当会导致数据损坏或读取失败。
JSON 模块
json 模块提供两组核心函数:操作字符串的 dumps / loads,以及直接对接文件对象的 dump / load。名称末尾的 s 代表 string,是区分二者的关键记忆点。
字符串层面的序列化与反序列化
json.dumps(obj) 将 Python 对象转为 JSON 格式的字符串,这一过程称为序列化(serializing)。json.loads(s) 则反向执行反序列化(deserializing)。
import json
x = [1, 'simple', 'list']
print(json.dumps(x)) # '[1, "simple", "list"]'
s = '{"name": "Alice", "age": 30, "active": true}'
print(json.loads(s)) # {'name': 'Alice', 'age': 30, 'active': True}
Python 类型与 JSON 类型的映射关系如下:
| Python | JSON |
|---|---|
dict | object |
list, tuple | array |
str | string |
int, float | number |
True / False | true / false |
None | null |
注意:JSON 的键必须是字符串,因此 Python 字典的整数键会被强制转为字符串;元组会被转为数组(反序列化时变回列表,信息丢失)。
data = {(1, 2): 'value'} # 元组作键
print(json.dumps(data)) # '{"[1, 2]": "value"}' ← 键被强制字符串化
文件层面的序列化与反序列化
dump(obj, fp) 和 load(fp) 直接读写文件对象,省去手动中转字符串的步骤。JSON 规范要求文件使用 UTF-8 编码,因此打开文件时必须显式指定 encoding='utf-8'。
employee = {
'company': 'Example Inc.',
'founded': 2018,
'team': [
{'name': 'Alice', 'role': 'backend'},
{'name': 'Bob', 'role': 'frontend'}
],
'active': True
}
# 写入
with open('data.json', 'w', encoding='utf-8') as f:
json.dump(employee, f)
# 读取
with open('data.json', 'r', encoding='utf-8') as f:
obj = json.load(f)
print(obj['team'][0]['name']) # Alice
格式化输出
dumps 和 dump 提供若干参数控制输出格式:
indent:缩进空格数,美化输出。设为None(默认)则压缩为一行。ensure_ascii=False:允许非 ASCII 字符(如中文)原样输出,而不是转义为\uXXXX。sort_keys=True:按键名排序,便于版本控制 diff。
with open('pretty.json', 'w', encoding='utf-8') as f:
json.dump(employee, f, ensure_ascii=False, indent=2, sort_keys=True)
自定义序列化
默认情况下,datetime 对象、自定义类实例等无法直接序列化,会抛出 TypeError。可通过 default 参数传入一个钩子函数,将不支持的对象转为可序列化类型:
from datetime import datetime
def custom_serializer(obj):
if isinstance(obj, datetime):
return obj.isoformat()
raise TypeError(f'Object of type {type(obj).__name__} is not JSON serializable')
now = {'created': datetime.now()}
print(json.dumps(now, default=custom_serializer))
# {"created": "2024-06-01T14:30:00.000000"}
反序列化时,可用 object_hook 将字典转为自定义对象,实现类型还原。
性能与安全边界
JSON 是纯文本格式,人类可读且语言无关,但不支持 Python 特有的对象类型(如集合、复数、任意类实例)。若需要在 Python 进程间保存完整对象图,标准库提供 pickle 模块,但它使用私有协议,不能与其他语言交互,且默认不安全——解序列化恶意构造的 pickle 数据可能执行任意代码。因此,永远不要对不信任来源的数据使用 pickle.load()。
CSV 模块
CSV(Comma-Separated Values)用换行分隔行、逗号分隔列,是最简单的表格格式。Python 的 csv 模块处理读写,并自动应对引号、转义和换行符等边界情况。
写入 CSV
csv.writer(fileobj) 返回一个 writer 对象,其 writerow(row) 方法写入单行,writerows(rows) 写入多行。row 是可迭代对象(通常为列表),每个元素转为字符串后输出。
import csv
staff = [
['姓名', '部门', '年龄'],
['Alice', '技术部', 28],
['Bob', '产品部', 26]
]
with open('staff.csv', 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerows(staff)
newline='' 至关重要。CSV 模块自己负责处理换行符,若使用平台默认换行(Windows 下为 \r\n),csv.writer 会在每行末尾再追加一个 \r,导致文件出现空行。传入 newline='' 将换行控制权完全交给 csv 模块,可生成符合 RFC 4180 标准的文件。
csv.writer 还支持 delimiter(分隔符,默认逗号)、quotechar(引号字符,默认 ")、quoting(引号策略)等参数。当字段包含逗号或换行时,模块会自动加引号包裹:
with open('complex.csv', 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerow(['备注', '包含,逗号\n和换行'])
# 输出:备注,"包含,逗号
# 和换行"
读取 CSV
csv.reader(fileobj) 返回一个迭代器,每次产生一行(字符串列表)。
with open('staff.csv', 'r', newline='', encoding='utf-8') as f:
reader = csv.reader(f)
for row in reader:
print(row)
# ['姓名', '部门', '年龄']
# ['Alice', '技术部', '28']
注意:所有读出的字段都是字符串,数字需要手动 int() / float() 转换。若第一行是表头,可用 csv.DictReader 将每行映射为字典,键取自表头:
with open('staff.csv', 'r', newline='', encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
print(f"{row['姓名']} — {row['部门']}")
DictReader 的字段名可通过 fieldnames 参数显式指定,或让模块自动从第一行推断。若表头缺失,建议手动传入 fieldnames 避免把数据行误当表头。
with open('no_header.csv', 'r', newline='', encoding='utf-8') as f:
reader = csv.DictReader(f, fieldnames=['name', 'dept', 'age'])
for row in reader:
print(row)
字典方式写入
csv.DictWriter 与 DictReader 对应,接受字典序列并按固定字段顺序输出:
with open('out.csv', 'w', newline='', encoding='utf-8') as f:
fieldnames = ['name', 'dept', 'age']
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
writer.writerow({'name': 'Alice', 'dept': '技术部', 'age': 28})
writer.writerows([
{'name': 'Bob', 'dept': '产品部', 'age': 26},
{'name': 'Carol', 'dept': '设计部', 'age': 25}
])
若字典缺少某个字段,DictWriter 默认写入空字符串;若多出未声明的字段,默认忽略。可通过 restval 和 extrasaction 参数调整这一行为。
编码与换行符处理
JSON 和 CSV 都是纯文本格式,编码问题尤为突出。
- JSON:RFC 8259 明确规定 JSON 文本必须是 UTF-8(无 BOM)。读写时始终使用
encoding='utf-8'。json.dumps的ensure_ascii=False让中文直接写入,避免\u4e2d\u6587这类转义,既减小体积又提高可读性。 - CSV:RFC 4180 未规定编码,但实际使用中 UTF-8 已成为默认选择。Excel 打开 UTF-8 CSV 时,若含 BOM(
utf-8-sig)可自动识别中文;否则可能需要手动选择编码。若目标用户主要使用 Excel,写入时可考虑encoding='utf-8-sig',它在文件头添加 BOM,帮助 Excel 正确识别。
换行符方面,JSON 的 dump 使用 \n 作为缩进换行,与平台无关。CSV 则必须将 newline='' 传给 open(),否则 Windows 平台会产生 \r\r\n 的畸形换行。
总结
json 模块的 dumps/loads 操作字符串,dump/load 操作文件,配合 ensure_ascii=False 和 indent 可生成人类可读的 UTF-8 文本。自定义对象通过 default 钩子序列化。csv 模块的 reader/writer 处理列表,DictReader/DictWriter 处理字典,读写时都必须指定 newline='' 防止空行。两种格式都建议显式声明 encoding='utf-8',JSON 用于层级数据交换,CSV 用于扁平表格互通,各安其位。