Python 环境加载中…

文件、csv 与 json

4 道练习题·预计 60 分钟·做对一题解锁下一段

完整通读本章 →

教学 01 / 09· 已读

前言

前面学了模块,知道了怎么用 import ,把别人写好的工具拿来用。有了这个本事,就可以回头解决记账本的一个老问题了。

记账本一路写到 v3,能记、能算、能汇总,输错了也不怕。可它一直有个老毛病:程序一关,记的账就全没了。每次打开,都得从头记起。

这一章,咱们就来治好它:把账存进文件,下次打开还在。

要做到这一点,得用上两个 Python 自带的模块:csv 和 json 。那要不要先把这两个模块从头到尾背一遍呢?不用。第 9 章说过,模块不用一个一个地背,知道它能干什么,用的时候再查就行。这一章就是这么做的:带着「把账存起来」这个需求,边用边查。

先给各位童鞋打个预防针:文件读写一开始会有点绕,打开方式、编码、换行,每一样都可能出岔子。出了错也别着急,按实战 3 的办法,先看报错的最后一行,再找行号,大多数问题都能自己解决。

学这一章之前,需要先学完 实战 4 的记账本 v3,以及 第 9 章 的 import 和「几个常用的标准库」里的 json。

目录

这一章会学到:

  • 一、程序一关,账就没了——变量只活在程序运行的时候
  • 二、先学会读写一个文件——open() 、with 和 encoding
  • 三、账本存成 csv——一行一笔账,表格软件也能打开
  • 四、换成 json 试试——存进去是什么类型,读出来还是什么类型
  • 五、常见坑
  • 六、自己查一次文档——csv 文件为什么要写 newline=''
  • 七、记账本 v4——给 Ledger 加上 save() 和 load()
  • 八、小结与回看

学完这一章,你的记账本会把每一笔账存进文件,下次打开程序,接着上次的账往下记。

教学 02 / 09· 已读

一、程序一关,账就没了

程序一关,账为什么就没了呢?先回顾一下实战 4 的记账本 v3:记账的时候,账都存在 book.entries 这个列表里。

问题就出在这儿。列表、字典这些变量,都放在电脑的内存里。内存有个特点:速度快,可程序一结束,里面的东西就全清空了。所以每次运行记账本,book.entries 都是从一个空列表开始。

那怎样才能让账留下来呢?得把它存进文件。文件放在硬盘上,程序关了、电脑关了,它都还在。下次打开程序,再把文件里的账读回来就行。

所以这一章要做两件事:

  1. 存:程序结束之前,把账写进文件。
  2. 读:程序开始的时候,把文件里的账读回来。

不过,在网页里运行代码,有一点要先说清楚:网页里的 Python 碰不到你电脑上的硬盘。这一章的代码在网页里写出的文件,存在浏览器临时准备的一块地方,刷新页面就没了。所以,想亲眼看到「关掉程序,下次打开账还在」,要把代码存成 .py 文件,在自己电脑上运行。运行以后,文件会出现在你运行程序时所在的文件夹里。

存也好,读也好,都得先学会怎么读写文件。咱们就从最简单的文本文件开始。

教学 03 / 09· 已读

二、先学会读写一个文件

咱们先写一个文件,再把它读出来,看看都要用到哪些写法:

Python· 可在网页运行
with open('ledger.txt', 'w', encoding='utf-8') as f:
    f.write('奶茶 12.5\n')
    f.write('午饭 25\n')

with open('ledger.txt', encoding='utf-8') as f:
    print(f.read())

输出的结果:

文本
奶茶 12.5
午饭 25

这几行代码里,新东西不少,咱们一样一样来看:

  • open('ledger.txt', 'w', encoding='utf-8') :打开一个叫 ledger.txt 的文件。第二个参数是打开方式,'w' 是写(write):文件不存在就新建一个,已经存在就清空了重写。不写这个参数,就是 'r' 读(read)。
  • encoding='utf-8' :用哪种编码来存文字。有中文的时候一定要写上,不然换一台电脑读,可能就是一堆乱码,甚至直接报错。第五节「常见坑」会细说。
  • with ... as f: :打开文件,给它起个名字叫 f 。缩进里的代码执行完,Python 会自动把文件关掉。with 是怎么做到的,第 21 章会讲;现在先记住这个写法:读写文件,都用 with 。
  • f.write() :把一段文字写进文件。它不会自动换行,要换行得自己加 \n 。
  • f.read() :把整个文件读成一个字符串。

细心的童鞋会发现,输出的最后多了一个空行。这是因为文件最后有一个 \n ,print() 打印完,自己又换了一次行。

f.read() 是一下子读出整个文件。要是想一行一行地处理呢?文件也可以一行一行地读,用 for 遍历它就行:

Python· 可在网页运行
with open('ledger.txt', 'w', encoding='utf-8') as f:
    f.write('奶茶 12.5\n')
    f.write('午饭 25\n')

with open('ledger.txt', encoding='utf-8') as f:
    for line in f:
        print(line.strip())

输出的结果:

文本
奶茶 12.5
午饭 25

这里为什么要先 strip() 一下呢?因为读出来的每一行,末尾都带着换行符 \n ,所以先用 strip() 把它去掉。

前面说过,用 'w' 打开,已经存在的文件会被清空了重写。那要是想在文件后面接着写呢?这时候就用 'a' ,也就是追加(append):

Python· 可在网页运行
with open('ledger.txt', 'w', encoding='utf-8') as f:
    f.write('奶茶 12.5\n')

with open('ledger.txt', 'a', encoding='utf-8') as f:
    f.write('午饭 25\n')

with open('ledger.txt', encoding='utf-8') as f:
    print(f.read())

输出的结果:

文本
奶茶 12.5
午饭 25

可以看到,第二次打开用的是 'a' ,午饭接在了奶茶后面,先写的那一笔没有被冲掉。

这样一来,文本文件就能存账了。可是读回来以后,奶茶 12.5 只是一整行字符串,名称和金额还得自己拆开。要是再加上类别,就更麻烦了。

那么,有没有现成的格式,专门用来存这种一条一条的数据呢?有,csv 和 json 就是最常用的两种。下一节先来看 csv。

教学 04 / 09

三、账本存成 csv

先说 csv。它是一种很常见的表格格式:一行一条记录,每一项用逗号隔开。Excel、WPS 这些表格软件都能直接打开它。

那怎么把账写成 csv 呢?Python 自带一个 csv 模块。咱们的每一笔账正好是一个字典,所以用 csv.DictWriter 来写最方便:

Python· 可在网页运行
import csv

entries = [
    {'item': '奶茶', 'category': '餐饮', 'cost': 12.5},
    {'item': '地铁', 'category': '交通', 'cost': 4},
]
with open('ledger.csv', 'w', encoding='utf-8', newline='') as f:
    writer = csv.DictWriter(f, fieldnames=['item', 'category', 'cost'])
    writer.writeheader()
    writer.writerows(entries)

with open('ledger.csv', encoding='utf-8') as f:
    print(f.read())

输出的结果:

文本
item,category,cost
奶茶,餐饮,12.5
地铁,交通,4

对照输出,看看写文件的那几行各干了什么:

  • fieldnames 告诉它有哪几列,按什么顺序排。
  • writeheader() 先写第一行,也就是表头。
  • writerows(entries) 把每一笔账写成一行。

另外,这回 open() 里多了一个 newline='' 。它是干什么的,先不急,照着写就行,第六节查文档的时候再细说。

写进去了,还得能读回来。读用的是 csv.DictReader ,每一行会变回一个字典:

Python· 可在网页运行
import csv

entries = [
    {'item': '奶茶', 'category': '餐饮', 'cost': 12.5},
    {'item': '地铁', 'category': '交通', 'cost': 4},
]
with open('ledger.csv', 'w', encoding='utf-8', newline='') as f:
    writer = csv.DictWriter(f, fieldnames=['item', 'category', 'cost'])
    writer.writeheader()
    writer.writerows(entries)

with open('ledger.csv', encoding='utf-8', newline='') as f:
    for row in csv.DictReader(f):
        print(row)

输出的结果:

文本
{'item': '奶茶', 'category': '餐饮', 'cost': '12.5'}
{'item': '地铁', 'category': '交通', 'cost': '4'}

是不是发现什么不对?存进去的时候,cost 是数字 12.5 ,读回来却变成了字符串 '12.5' 。

这是为什么呢?因为 csv 文件里存的全是文字,它可不记得哪一列原来是数字。这和实战 1 的 input() 是一回事:读进来的都是字符串,要当数字用,就得自己转换。所以下面读的时候,顺手把每一笔的金额转成数字:

Python· 可在网页运行
import csv

entries = [
    {'item': '奶茶', 'category': '餐饮', 'cost': 12.5},
    {'item': '地铁', 'category': '交通', 'cost': 4},
]
with open('ledger.csv', 'w', encoding='utf-8', newline='') as f:
    writer = csv.DictWriter(f, fieldnames=['item', 'category', 'cost'])
    writer.writeheader()
    writer.writerows(entries)

loaded = []
with open('ledger.csv', encoding='utf-8', newline='') as f:
    for row in csv.DictReader(f):
        row['cost'] = float(row['cost'])
        loaded.append(row)
print(loaded)
print(sum([entry['cost'] for entry in loaded]))

输出的结果:

文本
[{'item': '奶茶', 'category': '餐饮', 'cost': 12.5}, {'item': '地铁', 'category': '交通', 'cost': 4.0}]
16.5

转换以后,金额又变回了数字,合计也能算出来了。

csv 还有一点要注意:要把 csv 文件拿给 Excel 打开的话,把 encoding='utf-8' 换成 encoding='utf-8-sig' 。不然 Excel 可能认不出这是 UTF-8 编码,中文就显示成了乱码。

到这里,账已经能存成 csv,再读回来了。只是读回来的数字还得自己转换,多少有点麻烦。下一节换成 json,看看能不能省掉这一步。在那之前,先做一道练习:用 csv.DictReader 读一个文件,把金额加起来。

练习 1 / 4读 csv,算合计题目有问题?

编辑器上面已经把两天的账分别写进了 day1.csv 和 day2.csv 。请写一个函数 csv_total(path) :用 csv.DictReader 读这个文件,返回所有金额的合计。

注意:从 csv 读回来的金额是字符串,要先转换成数字再加。

运行后的输出应该是:

文本
第一天:20.50 元
第二天:79.50 元
main.py
可编辑
做对当前题解锁下一段 ·04

推荐一个,全网最全最友好的中文 AI 编程指南: coding.stormzhang.ai

学习目录