第5章 5.4 JSON/CSV/YAML 数据格式
「上节课我们学会了用 with 打开文件、读写内容,感觉自己已经是「文件操作小能手」了。但你有没有遇到过这种情况——想保存一个复杂的资料库(比如通讯录、游戏存档、API 返回的数据),却不知道该用什么格式?直接存成 txt 像个杂货铺,找东西全靠肉眼;存成 Excel 又太重,编程不友好。这节课我们就来解决这个「文件格式选择困难症」,让你在不同场景下都能选出最合适的数据存储方式。
你有没有想过这样一个问题:你玩游戏的存档、去哪儿网查的机票数据、甚至微信小程序的配置信息,它们是怎么存储的?为什么有些文件用记事本打开能看懂,有些打开是乱码?学完这节课,你就能回答这些问题了。
🎯 开场 3 分钟:为什么要学这个?
一个真实的场景
想象你是一个小淘宝店老板,每天要处理一堆订单:
# 你的订单数据长这样
订单1:买家=张三,商品=鼠标×2,单价=89,总价=178
订单2:买家=李四,商品=键盘×1,单价=199,总价=199
订单3:买家=王五,商品=耳机×3,单价=45,总价=135
你最开始用txt记录,发现找「李四的订单」得一行行看;换成Excel又发现,用Python程序来处理订单时,每次都要先打开Excel软件,麻烦死了。
痛点1:数据格式选不对,程序读起来费劲,人看起来也费劲。
后来你发现了一个神器——CSV格式,用逗号分隔的数据文件。程序读起来方便,用Excel打开也依然是整整齐齐的表格。但还没完,你想存更复杂的数据,比如商品的详情图片链接、买家备注等,CSV就有点力不从心了……
痛点2:不同场景需要不同的数据格式,没有一种格式是万能的。
学完这节课你能解决什么
- 能根据场景选择最合适的数据格式(JSON/CSV/YAML)
- 能读写这三种格式的文件
- 能把它们和Python的字典、列表无缝转换
- 能写一个真正有用的小工具(比如待办清单管理器)
🧱 基础 25 分钟:核心概念
5.4.1 CSV:表格数据的老大哥
是什么:CSV = Comma-Separated Values,用逗号分隔的值。你可以理解为「用逗号当栅栏围起来的Excel」。
为什么要用:当你有行列分明的数据时,CSV是最简单高效的选择。比如订单表、学生成绩表、商品库存表。
类比:就像超市的价签,每行一个商品,商品的不同信息(名称、价格、库存)用逗号隔开,顾客一眼就能看清楚那一行是什么。
怎么用:
import csv
# 写入CSV文件
with open('orders.csv', 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerow(['买家', '商品', '数量', '单价', '总价'])
writer.writerow(['张三', '鼠标', 2, 89, 178])
writer.writerow(['李四', '键盘', 1, 199, 199])
writer.writerow(['王五', '耳机', 3, 45, 135])
# 读取CSV文件
with open('orders.csv', 'r', encoding='utf-8') as f:
reader = csv.reader(f)
for row in reader:
print(row)
运行结果:
['买家', '商品', '数量', '单价', '总价']
['张三', '鼠标', 2, 89, 178]
['李四', '键盘', 1, 199, 199]
['王五', '耳机', 3, 45, 135]
代码解释:writerow() 写入一行数据,reader() 把每一行变成一个列表。newline='' 是为了避免Windows系统写入时多空一行。
5.4.2 JSON:互联网数据的事实标准
是什么:JSON = JavaScript Object Notation,是一种轻量级的数据交换格式。看起来像Python的字典和列表的组合。
为什么要用:当你需要存储嵌套的、层级化的数据时,JSON比CSV更灵活。比如一个完整的用户资料(包含基本信息、地址列表、订单历史等嵌套结构)。
类比:想象一个俄罗斯套娃,大娃里面有个中娃,中娃里面有个小娃。JSON就能完美表达这种嵌套关系,而CSV只能表达扁扁的一层。
怎么用:
import json
# 写入JSON文件
data = {
'店铺名称': '小明数码店',
'员工': ['张三', '李四', '王五'],
'月销售额': 125000,
'爆款商品': {
'名称': '无线鼠标',
'价格': 89,
'库存': 200
}
}
with open('shop.json', 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
# 读取JSON文件
with open('shop.json', 'r', encoding='utf-8') as f:
loaded_data = json.load(f)
print(loaded_data['店铺名称'])
print(loaded_data['爆款商品']['名称'])
运行结果:
小明数码店
无线鼠标
代码解释:json.dump() 写入文件,json.load() 读取文件。ensure_ascii=False 让中文正常显示,indent=2 让输出的JSON格式化,看起来更清晰。

5.4.3 YAML:人类友好型配置文件
是什么:YAML = YAML Ain't Markup Language,是一种人类友好程度爆棚的数据格式。它的设计理念是「用缩进代替括号,用换行代替逗号」。
为什么要用:当你写的配置文件要给别人看、或者自己经常手动修改时,YAML比JSON更易读。它经常用于项目配置、Docker编排、CI/CD流水线等。
类比:就像Markdown,用空格和换行来组织内容,看起来像文章一样自然,而不是像代码那样充满符号。
怎么用:
import yaml
# 写入YAML文件
config = {
'数据库': {
'主机': 'localhost',
'端口': 3306,
'用户名': 'root',
'密码': '123456'
},
'日志级别': 'INFO',
'是否开启调试': true
}
with open('config.yaml', 'w', encoding='utf-8') as f:
yaml.dump(config, f, allow_unicode=True, default_flow_style=False)
# 读取YAML文件
with open('config.yaml', 'r', encoding='utf-8') as f:
loaded_config = yaml.safe_load(f)
print(f"日志级别: {loaded_config['日志级别']}")
print(f"数据库主机: {loaded_config['数据库']['主机']}")
运行结果:
日志级别: INFO
数据库主机: localhost
代码解释:yaml.dump() 写入,yaml.safe_load() 读取。safe_load() 会拒绝执行文件中可能的危险命令,比 load() 更安全。

5.4.4 三种格式的快速对比
| 格式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| CSV | 表格数据、批量导入导出 | 简单、Excel可直接打开 | 不支持嵌套结构 |
| JSON | API数据、复杂数据结构 | 互联网标准、支持嵌套 | 读起来没有YAML直观 |
| YAML | 配置文件、人类要读的设置 | 人类超友好、可读性最强 | 缩进要求严格 |
5.4.5 与 dict 的互转
这是最关键的部分:无论哪种格式,读进来都是dict,写出去都是从dict写。
import json
# dict → JSON字符串(内存中操作)
person = {'name': '小明', 'age': 18}
json_str = json.dumps(person) # dict转字符串
print(json_str) # {"name": "小明", "age": 18}
# JSON字符串 → dict
parsed = json.loads(json_str) # 字符串转dict
print(parsed['name']) # 小明
类比:dict是「原材料」,JSON/YAML是「原材料做成的成品」。读写文件就是在「原材料」和「成品」之间转换。
🔥 实战 35 分钟:3 个递进的小项目
项目 1(5分钟):CSV 版通讯录
需求:做一个简易通讯录,能添加联系人和查看所有联系人。
import csv
import os
def add_contact(name, phone, email):
"""添加联系人到通讯录"""
file_exists = os.path.exists('contacts.csv')
with open('contacts.csv', 'a', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
if not file_exists:
writer.writerow(['姓名', '电话', '邮箱']) # 首次写入表头
writer.writerow([name, phone, email])
print(f"✓ 已添加 {name}")
def show_all_contacts():
"""显示所有联系人"""
if not os.path.exists('contacts.csv'):
print("通讯录为空")
return
with open('contacts.csv', 'r', encoding='utf-8') as f:
reader = csv.reader(f)
for i, row in enumerate(reader):
if i == 0:
print(f"{'姓名':<10} {'电话':<15} {'邮箱':<20}")
print("-" * 50)
else:
print(f"{row[0]:<10} {row[1]:<15} {row[2]:<20}")
# 测试
add_contact('张三', '13800138000', 'zhangsan@example.com')
add_contact('李四', '13900139000', 'lisi@example.com')
show_all_contacts()
运行结果:
✓ 已添加 张三
✓ 已添加 李四
姓名 电话 邮箱
--------------------------------------------------
张三 13800138000 zhangsan@example.com
李四 13900139000 lisi@example.com
一句话解释:csv.writer 的 writerow() 负责把列表变成一行,csv.reader 的迭代器负责把每一行变回列表。
项目 2(15分钟):JSON 版任务清单管理器
需求:实现增删改查功能,数据持久化到JSON文件。
import json
import os
TODO_FILE = 'todo_list.json'
def load_todos():
"""从文件加载任务清单"""
if os.path.exists(TODO_FILE):
with open(TODO_FILE, 'r', encoding='utf-8') as f:
return json.load(f)
return []
def save_todos(todos):
"""保存任务清单到文件"""
with open(TODO_FILE, 'w', encoding='utf-8') as f:
json.dump(todos, f, ensure_ascii=False, indent=2)
def add_task(task):
"""添加任务"""
todos = load_todos()
todos.append({'内容': task, '完成': False})
save_todos(todos)
print(f"✓ 已添加任务:{task}")
def show_tasks():
"""显示所有任务"""
todos = load_todos()
if not todos:
print("任务清单是空的")
return
print(f"\n{'序号':<4} {'任务':<20} {'状态':<6}")
print("-" * 35)
for i, todo in enumerate(todos, 1):
status = "✓" if todo['完成'] else "○"
print(f"{i:<4} {todo['内容']:<20} {status:<6}")
def complete_task(index):
"""标记任务为完成"""
todos = load_todos()
if 0 < index <= len(todos):
todos[index-1]['完成'] = True
save_todos(todos)
print(f"✓ 已完成任务:{todos[index-1]['内容']}")
else:
print("无效的任务序号")
def delete_task(index):
"""删除任务"""
todos = load_todos()
if 0 < index <= len(todos):
deleted = todos.pop(index-1)
save_todos(todos)
print(f"✓ 已删除任务:{deleted['内容']}")
else:
print("无效的任务序号")
# 测试
add_task("写完Python作业")
add_task("给妈妈打电话")
add_task("整理房间")
show_tasks()
complete_task(1)
delete_task(3)
show_tasks()
运行结果:
✓ 已添加任务:写完Python作业
✓ 已添加任务:给妈妈打电话
✓ 已添加任务:整理房间
序号 任务 状态
-----------------------------------
1 写完Python作业 ○
2 给妈妈打电话 ○
3 整理房间 ○
✓ 已完成任务:写完Python作业
✓ 已删除任务:整理房间
序号 任务 状态
-----------------------------------
1 写完Python作业 ✓
2 给妈妈打电话 ○
一句话解释:json.load() 读进来是dict列表,对列表进行增删改后,json.dump() 再写回去。
项目 3(15分钟):数据格式转换小工具
需求:做一个命令行工具,能把 CSV 转成 JSON,或者把 JSON 转成 CSV。
import csv
import json
import sys
import os
def csv_to_json(csv_file, json_file):
"""CSV转JSON"""
data = []
with open(csv_file, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f) # 用DictReader自动把表头当key
for row in reader:
data.append(row)
with open(json_file, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
print(f"✓ 已转换:{csv_file} → {json_file}")
print(f" 共转换 {len(data)} 条记录")
def json_to_csv(json_file, csv_file):
"""JSON转CSV"""
with open(json_file, 'r', encoding='utf-8') as f:
data = json.load(f)
if not data:
print("JSON文件为空")
return
# 用第一条记录的key作为CSV表头
headers = list(data[0].keys())
with open(csv_file, 'w', newline='', encoding='utf-8') as f:
writer = csv.DictWriter(f, fieldnames=headers)
writer.writeheader()
writer.writerows(data)
print(f"✓ 已转换:{json_file} → {csv_file}")
print(f" 共转换 {len(data)} 条记录")
def main():
if len(sys.argv) != 4:
print("用法:")
print(" CSV转JSON: python converter.py csv2json input.csv output.json")
print(" JSON转CSV: python converter.py json2csv input.json output.csv")
return
command = sys.argv[1]
input_file = sys.argv[2]
output_file = sys.argv[3]
if not os.path.exists(input_file):
print(f"错误:找不到文件 {input_file}")
return
if command == 'csv2json':
csv_to_json(input_file, output_file)
elif command == 'json2csv':
json_to_csv(input_file, output_file)
else:
print(f"未知命令:{command}")
if __name__ == '__main__':
main()
测试方法:
# 先创建测试用的CSV文件
echo "姓名,年龄,城市" > test.csv
echo "张三,25,北京" >> test.csv
echo "李四,30,上海" >> test.csv
# 运行转换
python converter.py csv2json test.csv test.json
python converter.py json2csv test.json output.csv
一句话解释:csv.DictReader 能把CSV的表头自动变成字典的key,这才是CSV转JSON的正确姿势。
💪 进阶 20 分钟:常见坑 + 性能小贴士
坑 1:CSV 中文乱码
# ❌ 错误写法(Windows默认编码不是utf-8)
with open('data.csv', 'w') as f:
writer = csv.writer(f)
writer.writerow(['姓名', '城市'])
# ✅ 正确写法
with open('data.csv', 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerow(['姓名', '城市'])
解释:Windows默认用GBK编码,中文会变成乱码。Mac/Linux一般没问题,但写上 encoding='utf-8' 是好习惯。
坑 2:JSON 中文变成 Unicode 转义
# ❌ 错误写法
data = {'name': '小明', 'city': '北京'}
json_str = json.dumps(data)
print(json_str) # {"name": "\u5c0f\u660e", "city": "\u5317\u4eac"}
# ✅ 正确写法
json_str = json.dumps(data, ensure_ascii=False)
print(json_str) # {"name": "小明", "city": "北京"}
解释:ensure_ascii=False 让中文保持原样输出,不转换成 \u Unicode转义。
坑 3:JSON 文件读写混用
# ❌ 错误:写入用dump,读取用loads(字符串操作)
data = {'x': 1}
with open('test.json', 'w') as f:
json.dump(data, f) # 写入文件
with open('test.json', 'r') as f:
content = f.read()
result = json.loads(content) # 从字符串解析
# ✅ 正确:写入用dump,读取用load(文件操作)
with open('test.json', 'r') as f:
result = json.load(f) # 直接从文件读取
解释:json.load() 直接读取文件并解析成dict,比 read() + loads() 少一步。
坑 4:YAML 缩进必须是空格
# ❌ 错误:YAML不支持tab缩进
config = """
database:
host: localhost
"""
# ✅ 正确:全部用空格
config = """
database:
host: localhost
"""
解释:YAML对缩进非常敏感,Tab在YAML中是语法错误,必须用空格。
坑 5:CSV 多写入空行
# ❌ 错误:Windows下会多空行
with open('test.csv', 'w') as f:
writer = csv.writer(f)
writer.writerow(['a', 'b'])
writer.writerow(['c', 'd'])
# ✅ 正确:加 newline=''
with open('test.csv', 'w', newline='') as f:
writer = csv.writer(f)
writer.writerow(['a', 'b'])
writer.writerow(['c', 'd'])
解释:这是Python 3在Windows上的特性,不加 newline='' 会在每行后面多加一个 \r\n。
性能小贴士:大量数据用流式处理
如果你有100万行的CSV要处理,不要用 csv.reader 一次性读进内存:
# ✅ 推荐:逐行处理
with open('big_data.csv', 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader: # 一行一行处理,不占内存
process(row)
解释:Python的生成器模式,按需加载,不会爆内存。
调试技巧:print 大法
数据格式转换出问题?先 print() 看看中间变量:
# 写数据转换时,先看看读进来是什么
with open('data.csv', 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
print(type(row), row) # 看看row是什么类型、什么内容
✏️ 练习题
练习 1(1分钟):CSV 基础添加
- 输入:运行以下代码,查看 students.csv 的内容
import csv
with open('students.csv', 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerow(['姓名', '分数'])
writer.writerow(['小红', 95])
- 预期输出:一个包含两行的CSV文件
- 提示:直接用Excel或记事本打开看看
练习 2(2分钟):JSON 读取
- 输入:把上一章你保存的文件内容用 json.load() 读出来
- 预期输出:Python字典对象
- 提示:先确认文件是JSON格式且合法
练习 3(3分钟):给通讯录加搜索
- 输入:在项目1的通讯录中,用 if '关键字' in row[0] 搜索姓张的人
- 预期输出:张三的信息
- 提示:复用项目1的代码,加一个搜索函数
练习 4(4分钟):YAML 配置读取
- 输入:用 yaml.safe_load() 读取以下内容:
server:
host: 127.0.0.1
port: 8080
- 预期输出:
{'server': {'host': '127.0.0.1', 'port': 8080}} - 提示:YAML会自动把数字字符串转成整数
练习 5(5分钟):修复报错
- 输入:运行以下代码会报错
import csv
with open('test.csv', 'w') as f:
writer = csv.writer(f)
writer.writerow(['姓名', '小明', '年龄', 18])
- 预期输出:报错或乱码
- 提示:检查encoding参数和newline参数
作业:做一个「第5章 5.4 JSON/CSV/YAML 数据格式实战工具」
做一个个人消费记录器,要求:
- 需求描述:记录你每天的消费情况,支持按月份统计,自动生成月度报表
- 功能点:
1. 添加消费记录(日期、类别、金额、备注)
2. 查看所有记录
3. 按月份筛选记录
4. 统计某月总消费 - 数据格式:JSON存储(方便嵌套结构)
- 加分项:
1. 消费分类统计(吃/穿/住/行各花了多少)
2. 导出CSV报表(方便用Excel打开) - 验收标准:
- 能成功添加记录并持久化保存
- 重启程序后数据还在
- 月份筛选和统计功能正常
- 代码有适当注释
📚 总结 + 资源
这节课学到的3个核心点:
- CSV是表格数据的最佳拍档——简单、通用、Excel无缝支持
- JSON是复杂数据的瑞士军刀——互联网数据格式的事实标准
- YAML是人类友好的配置文件——可读性满分,缩进是生命线
延伸学习资源:
- Python官方文档 csv模块——最权威的API参考
- Python官方文档 json模块——进阶用法都在这里
- 《Python编程:从入门到实践》 第9章——有更多实战项目可以练手
互动钩子:
你平时用Excel还是备忘录记账?有没有遇到「想用程序分析但导入导出太麻烦」的情况?评论区聊聊你是怎么解决数据管理问题的,老粉优先回复!
下节预告:
学完这节课,你已经能熟练读写各种格式的文件了。但万一文件不存在、JSON格式有误、或者用户输入了乱七八糟的数据……程序直接崩溃给你看。下一章「异常处理」就是来解决这个问题的——让你的程序遇到错误也能优雅地活下去,而不是直接红屏报错退出。

评论(0)