CSV 就是「逗号分隔的纯文本」,没有任何格式、没有类型、没有颜色。正因为什么都没有,它才谁也认。
读回来的每个格子都是字符串,包括数量和单价。要算钱得自己 int() 或 float()。
Excel 打开 CSV 乱码,是因为文件少了三个字节的 BOM。写 utf-8-sig 就有,写 utf-8 就没有。
上一篇把数据装进了字典,这一篇把它落到文件里。 教材第 7 章后三节是【创建 CSV 文件】【使用 Google 云端试算表保存数据】 还有一节【从 Python 程序访问 Google 试算表】。 CSV 能真跑, 云试算表不行——那一步需要 Google 账号和服务账号密钥,标了「平台原样 · 本沙箱不执行」。 教材把云试算表放在这一章,想说的一句话是: 存储介质可以换,数据的形状不变。不管存 CSV 还是存云端表格,你脑子里那张表还是四列。
一、CSV 是什么
CSV 的全名是逗号分隔值。一行一条记录,一行里的字段用逗号隔开,第一行是表头。就这样,没有别的规矩。
演示第一段写出来的文件 176 字节,打开就是四行数据加一行表头:日期,品名,数量,单价,下面是四行。
它没有任何格式、没有类型、没有颜色、没有合并单元格。 正因为什么都没有,它才谁也认——Excel、
Numbers、数据库导入工具、老掉牙的报表系统,全都能读。
写的时候用 csv.writer,不是自己拼字符串。为什么?因为字段里可能有逗号。
品名写成「螺栓, 加长版」,你自己拼就多出一列,csv.writer 会给它加引号。
还有一个细节:Windows 上要写 newline='',不加的话每行会多一个空行。
这是 csv 模块文档里明确要求的,不是可选。
二、读回来全是字符串
演示第二段读回来 5 行(含表头),第一行是表头,
第二行是 ['2026-11-01', '六角螺栓 M8', '120', '1.5']。
注意 '120' 和 '1.5' 都带引号——它们是字符串,不是数字。
CSV 里没有类型这回事。文件里就是一堆字符,谁是数字谁不是,全靠你解读。
所以算钱之前必须自己 int() 或 float()。
这也是 CSV 和 JSON 的一个根本差别:
JSON 有类型,120 是数字、"120" 是字符串;CSV 全是字符串。
坏处是麻烦,好处是简单。而且因为它简单,出问题的时候一眼能看出来——用记事本打开就知道哪一行错了。
JSON 少一个括号,你得数半天。
三、用表头当键,取起来清楚
csv.reader 给的是列表,取值得数下标:row[1] 是品名。跟第 7.1 篇那个问题一样。
csv.DictReader 把每行变成字典,键就是表头。
演示里打出来是 {'日期': '2026-11-01', '品名': '六角螺栓 M8', '数量': '120', '单价': '1.5'}。
最大的好处是「以后表里插一列,按名字取的代码不用改」。 用下标的代码,插一列全错位。
写的时候也有对称的 DictWriter:先给 fieldnames,然后每行传一个字典。
它负责按表头顺序排好、该加引号的加引号。
用 DictReader/DictWriter 是处理表格数据的默认选择。 只有确定格式永不变的小脚本,
才值得省事用下标。
四、编码:Excel 打开是乱码的原因
这是 CSV 最常见的一个坑。你用 Python 写了个 CSV,中文正常;双击用 Excel 打开,一片乱码。
原因在文件最前面三个字节。
演示第四段打出来:utf-8-sig 开头是 b'\xef\xbb\xbf',纯 utf-8 开头是 b'\xe6\x97\xa5'。
那三个字节叫 BOM(字节顺序标记)。
Excel 靠它判断「这是 UTF-8」,没有它就当成系统默认编码(简体中文 Windows 上是 GBK),
于是中文全成乱码。
折中办法很清楚:给 Excel 看的 CSV 用 utf-8-sig,给程序读的用纯 utf-8。
为什么给程序读的不用 BOM?
因为 BOM 会给解析程序添麻烦——有的库会把它当成表名的第一个字符,于是你的 row['日期'] 取不到,
因为实际的键是 '\ufeff日期'。
这个 bug 极其难查:打印出来看着一模一样,== 比较却不相等。
所以写 CSV 给程序读时,一定要用纯 utf-8;读别人给的 CSV 时,先 lstrip('\ufeff') 一下更保险。
五、CSV 和 JSON 各多大
| 格式 | 字节数 | 人能直接看懂吗 | 带类型吗 |
|---|---|---|---|
| CSV | 168 | 能,双击就开 | 不带 |
| JSON | 340 | 能,但要会看括号 | 带 |
同一份四行数据,CSV 168 字节,JSON 340 字节。差一倍。 原因跟上一篇那个 8500 对 4542 是同一件事:CSV 的表头只写一次,JSON 每行都自带键名。 那 JSON 是不是更差?不是。JSON 每行自带键名,所以少一行不会错位; 而且它能表达嵌套结构,CSV 表达不了(CSV 里塞一个列表进去,只能拼成字符串)。
选哪个看下游。交给 Excel、交给别人的老系统,用 CSV;自己程序之间传、要嵌套结构,用 JSON。 两者都不是「更好」,是各管一段。知道自己在哪一段,比记「哪个先进」有用。
六、Google 试算表那一步是什么
教材接着讲把数据存到 Google 云端试算表。用的是 gspread 这个库。
流程是三步:拿一个服务账号的密钥文件 → 用密钥登录 → 按名字打开某张表,然后 append_row 追加一行。
key.json 等于一张长期通行证。 拿到它的人能以你的身份读写这些表,
所以绝对不能提交到代码仓库(第 10 章讲 Git 时会说怎么把它排除掉)。
云试算表的好处很实在:别人能直接打开看、能几个人一起改、手机上也能看。 坏处也很实在:要联网、要账号、要管密钥、还依赖那家公司的服务一直在。 本地 CSV 反过来:啥都不用配,但只能一个人看,传给别人得发文件。 这一段在演示里标了「平台原样 · 本沙箱不执行」——它需要 Google 账号和密钥,沙箱里没有。 代码是从教材里原样抄的,照贴到本机配上密钥就能跑。
七、动手 Demo:CSV:最土的表格格式,最耐用
先写一个 CSV,再用三种方式读回来:按行、按字典、看编码。 然后看同一份数据 CSV 和 JSON 各占多少字节。 最后看云试算表那段代码长什么样(平台原样,不执行)。
import csv, json, os, tempfile
d = tempfile.mkdtemp()
P = os.path.join(d, 'stock.csv')
rows = [['日期', '品名', '数量', '单价'],
['2026-11-01', '六角螺栓 M8', 120, 1.5],
['2026-11-01', '膨胀螺栓 10mm', 80, 2.3],
['2026-11-02', '不锈钢垫片', 500, 0.4],
['2026-11-02', '六角螺栓 M8', 60, 1.5]]
print('① 写一个 CSV')
with open(P, 'w', encoding='utf-8', newline='') as f:
csv.writer(f).writerows(rows)
print(' 文件 %d 字节' % os.path.getsize(P))
print(open(P, encoding='utf-8').read().strip())
print('② 读回来')
with open(P, encoding='utf-8', newline='') as f:
data = list(csv.reader(f))
print(' 行数 %d(含表头)' % len(data))
print(' 第一行', data[0])
print(' 第二行', data[1])
print(' 注意 120 和 1.5 都是字符串:', [type(x).__name__ for x in data[1]])
print('③ 用表头当键,取起来清楚')
with open(P, encoding='utf-8', newline='') as f:
for i, r in enumerate(csv.DictReader(f)):
if i < 2:
print(' ', r)
print(' DictReader 把每行变成字典,键就是表头。比数下标好——'
'以后表里插一列,按名字取的代码不用改。')
print('④ 编码:Excel 打开是乱码的原因')
sig = os.path.join(d, 'a_sig.csv')
raw = os.path.join(d, 'a_raw.csv')
for path, enc in ((sig, 'utf-8-sig'), (raw, 'utf-8')):
with open(path, 'w', encoding=enc, newline='') as f:
csv.writer(f).writerow(['日期', '品名'])
print(' utf-8-sig 开头三个字节', open(sig, 'rb').read(3))
print(' utf-8 开头三个字节', open(raw, 'rb').read(3))
print(' 就是这三字节 BOM,什么都没多')
with open(sig, encoding='utf-8', newline='') as f:
first_key = next(csv.DictReader(f)).popitem()[0]
print(' 但 BOM 会污染第一个键:%r' % first_key)
print('⑤ 同一份数据,CSV 和 JSON 各多大')
js = [dict(zip(rows[0], r)) for r in rows[1:]]
b1 = os.path.getsize(P)
b2 = len(json.dumps(js, ensure_ascii=False).encode('utf-8'))
print('| 格式 | 字节数 | 人能直接看懂吗 | 带类型吗 |')
print('|---|---|---|---|')
print('| CSV | %d | 能,双击就开 | 不带 |' % b1)
print('| JSON | %d | 能,但要会看括号 | 带 |' % b2)
print('⑥ Google 试算表那一步是什么')
print(' [平台原样 · 本沙箱不执行]')
print(' | # 本书第 7-5 节(平台原样,本沙箱不执行:需要 Google 账号与密钥)')
print(' | import gspread')
print(" | gc = gspread.service_account(filename='key.json')")
print(" | sh = gc.open('材料台账').sheet1")
print(" | sh.append_row(['2026-11-02', '不锈钢垫片', 500, 0.4])")
print(' | rows = sh.get_all_records()')
print(' key.json 是服务账号的凭证,等于一张长期通行证,别提交到代码仓库')
实跑输出:
--------------------------------------------------------------------
CSV:最土的表格格式,最耐用
--------------------------------------------------------------------
① 写一个 CSV
--------------------------------------------------------------------
文件 176 字节
日期,品名,数量,单价
2026-11-01,六角螺栓 M8,120,1.5
2026-11-01,膨胀螺栓 10mm,80,2.3
2026-11-02,不锈钢垫片,500,0.4
2026-11-02,六角螺栓 M8,60,1.5
② 读回来
--------------------------------------------------------------------
行数 5(含表头)
第一行 ['日期', '品名', '数量', '单价']
第二行 ['2026-11-01', '六角螺栓 M8', '120', '1.5']
读回来的每个格子都是字符串,包括数量和单价。
要算钱得自己 int() 或 float()——CSV 里没有类型这回事。
③ 用表头当键,取起来清楚
--------------------------------------------------------------------
{'日期': '2026-11-01', '品名': '六角螺栓 M8', '数量': '120', '单价': '1.5'}
{'日期': '2026-11-01', '品名': '膨胀螺栓 10mm', '数量': '80', '单价': '2.3'}
DictReader 把每行变成字典,键就是表头。比数下标好——以后表里插一列,按名字取的代码不用改。
④ 编码:Excel 打开是乱码的原因
--------------------------------------------------------------------
utf-8-sig 开头三个字节 b'\xef\xbb\xbf'
utf-8 开头三个字节 b'\xe6\x97\xa5'
就是这三字节 BOM,什么都没多
utf-8-sig 会在文件最前面写三个看不见的字节(BOM)。
Excel 靠这三个字节判断「这是 UTF-8」,没有它就当成系统默认编码,中文就成了乱码。
折中办法:给 Excel 看的 CSV 用 utf-8-sig,给程序读的用纯 utf-8。
BOM 会给解析程序添麻烦——有的库会把它当成表名的第一个字符。
⑤ 同一份数据,CSV 和 JSON 各多大
--------------------------------------------------------------------
| 格式 | 字节数 | 人能直接看懂吗 | 带类型吗 |
|---|---|---|---|
| CSV | 168 | 能,双击就开 | 不带 |
| JSON | 340 | 能,但要会看括号 | 带 |
CSV 更小,因为表头只写一次;JSON 更大,但每行自带键名,少一行也不会错位,嵌套结构也表达得了。
选哪个看下游:交给 Excel、交给别人的老系统,用 CSV;自己程序之间传,用 JSON。
两者都不是「更好」,是各管一段。
⑥ Google 试算表那一步是什么
--------------------------------------------------------------------
[平台原样 · 本沙箱不执行]
| # 本书第 7-5 节(平台原样,本沙箱不执行:需要 Google 账号与密钥)
| import gspread
| gc = gspread.service_account(filename='key.json')
| sh = gc.open('材料台账').sheet1
| sh.append_row(['2026-11-02', '不锈钢垫片', 500, 0.4])
| rows = sh.get_all_records()
key.json 是服务账号的凭证,等于一张长期通行证,别提交到代码仓库
云试算表的好处是别人能直接打开看、能一起改;坏处是要联网、要账号、要管密钥。
本地 CSV 反过来:啥都不用配,但只能一个人看。
教材把它放在这一章,是想说「存储介质可以换,数据的形状不变」——不管存 CSV 还是存云端表格,
你脑子里那张表还是四列。
第一段那个 newline='' 不是可选的。 不加它,Windows 上每行会多一个空行——这是 csv 模块文档里明确要求的。 第二段最后那行 type 打出来全是 str,说明 CSV 里没有类型。 要算钱必须自己转,而且转之前最好想一下「这格会不会是空的」。 第三段那个 DictReader 是本篇最实用的工具。按名字取,以后表里插一列不用改代码。 第四段是本篇的重点:BOM 那三个字节。不加它 Excel 认不出 UTF-8,中文乱码; 加了它,有的解析库会把 BOM 当成表名的第一个字符。所以给 Excel 用 sig,给程序用纯 utf-8。 第五段那个 168 对 340 的差距,原因是表头只写一次。跟上一篇那个 8500 对 4542 是同一件事。
30 秒实验
- 把演示第一段的
newline=''删掉,重跑,用记事本打开那个 CSV,看多了什么。 - 把演示第二段的
type(x).__name__换成直接sum(row[2] for row in data[1:]),看它报什么错, 再想想该怎么改。 - 把演示第五段的
ensure_ascii=False去掉,看 JSON 的字节数变多少。
常见误区
- 「CSV 里的数字读出来就是数字。」全是字符串。
'120' + 1会直接报错,'120' * 2会变成'120120'。 - 「CSV 用 utf-8 就行,Excel 能打开。
」简体中文 Windows 上的 Excel 会把纯 utf-8 当 GBK,中文乱码。
给 Excel 看的要用
utf-8-sig。 - 「JSON 比 CSV 大,所以 CSV 更好。 」JSON 大是因为每行自带键名,换来的是「少一行不错位」和「能表达嵌套」。 两者各管一段,没有谁更好。
自测三问(点开对答案)
1. 用 Python 写的 CSV,Excel 打开中文乱码,是哪里出了问题?怎么改? —— 文件最前面少了三个字节的 BOM(`\xef\xbb\xbf`),Excel 认不出这是 UTF-8, 就按系统默认编码(GBK)解读,中文全乱。写的时候用 `encoding='utf-8-sig'` 就有 BOM 了。 2. 为什么给程序读的 CSV 反而不能用 `utf-8-sig`? —— 因为 BOM 会被某些解析库当成第一个字段名的第一个字符。 于是 `row['日期']` 取不到,实际的键是 `'\ufeff日期'`。 打印出来看着一模一样,比较却不相等,这种 bug 极难查。 3. 同一份四行数据,CSV 168 字节、JSON 340 字节。那是不是应该一律用 CSV? —— 不是。 JSON 大是因为每行都自带键名,换来两件事:少一行不会整列错位, 而且能表达嵌套结构(字典套列表),CSV 表达不了。 交给 Excel 和老系统用 CSV,自己程序之间传用 JSON。第 7 章结束。下一篇进第 8 章:当函数越写越多,怎么用「类」把它们和数据捆在一起。
- ☐ (本篇完,下一篇 8.1 自定义类:远离意大利面条)