读写文件
程序一结束,变量就没了。文件会留下来:今天把点了几盏灯写进文件,明天的程序还能读回来。Python 用 open() 打开文件,然后从里面读,或者往里面写。
这一页的每个例子都在自己的一个虚拟文件夹里跑。文件夹一开始就有页面上列出的文件(比如下面的 lamps.txt),例子写的东西只留在这个例子里,怎么改都弄不坏。在你自己的电脑上,同样的代码读写的是真文件,就在你运行程序的那个文件夹里。
写文件
open(名字, "w") 打开一个文件来写,没有就新建一个。write 往里写文字,close 把文件关上。再用 open(名字) 打开来读,read 把里面的内容整个作为一个字符串返回:
1f = open("notes.txt", "w")2f.write("点了 4 盏灯\n")3f.write("还剩 2 桶灯油\n")4f.close()5 6f = open("notes.txt")7print(f.read(), end="")8f.close()
点了 4 盏灯 还剩 2 桶灯油
write 不会自己换行,所以每行末尾写上 \n。文件最后已经有一个换行了,end="" 让 print 别再多加一个。
close 很容易忘;要是在它之前出了错,文件可能一直关不上。with 代码块结束时会替你关上文件,就算是被异常打断也一样。从现在起都这样打开文件:
1with open("notes.txt", "w") as f:2 f.write("点了 4 盏灯\n")3 f.writelines(["还剩 2 桶灯油\n", "灯泡 7 个\n"])4print(f.closed)5 6with open("notes.txt") as f:7 print(f.read(), end="")
True 点了 4 盏灯 还剩 2 桶灯油 灯泡 7 个
writelines 把列表里的字符串一个接一个写进去;和 write 一样,它自己不加 \n。
读文件
文件夹里本来就有这个文件,这一页的每个例子一开始都有它:
榆树街 橡树巷 磨坊路 港湾路
read() 把整个文件作为一个字符串返回,.splitlines() 再把它切成一行一行。readline() 只读下一行,\n 也带着;readlines() 把剩下的行放进一个列表:
1with open("lamps.txt") as f:2 text = f.read()3print(text.splitlines())4 5with open("lamps.txt") as f:6 print(repr(f.readline()))7 print(f.readlines())
['榆树街', '橡树巷', '磨坊路', '港湾路'] '榆树街\n' ['橡树巷\n', '磨坊路\n', '港湾路\n']
repr() 显示字符串的时候连引号和 \n 一起显示,想看清到底读到了什么时很有用。最常用的是直接遍历文件,一次拿一行,多大的文件都能这么读:
1with open("lamps.txt") as f:2 for line in f:3 print("点亮" + line.strip())
点亮榆树街 点亮橡树巷 点亮磨坊路 点亮港湾路
不写 .strip(),有什么不一样?
1with open("lamps.txt") as f:2 for line in f:3 print(line)
每个 line 末尾还带着 \n,print 自己又换一次行,所以每条街后面都跟着一个空行。.strip()(或者 .rstrip("\n"))能把它去掉。
文件模式和追加
open 的第二个参数是模式:
| 模式 | 意思 | 文件已经有了 | 文件还没有 |
|---|---|---|---|
"r" | 读(不写就是它) | 从头读 | FileNotFoundError |
"w" | 写 | 先清空 | 新建 |
"a" | 追加 | 加在末尾 | 新建 |
"w" 要小心:用它一打开,原来的内容就没了,你一个字还没写。想往文件里加东西,用 "a":
1with open("lamps.txt", "a") as f:2 f.write("采石场路\n")3with open("lamps.txt") as f:4 print(f"{len(f.readlines())} 条街")5 6with open("lamps.txt", "w") as f:7 f.write("矿场大门\n")8with open("lamps.txt") as f:9 print(f.read(), end="")
5 条街 矿场大门
小技巧: 在自己电脑上,文件里有汉字时,加上 encoding="utf-8":open("lamps.txt", "a", encoding="utf-8")。不然有的系统(尤其是 Windows)会用别的编码读写,汉字就成了乱码。
文件不存在
读一个不存在的文件,会抛出 FileNotFoundError。用第 21 课的 try 接住它:这里没有存档,就从头开始。
1try:2 with open("save.txt") as f:3 level = f.read()4except FileNotFoundError:5 level = "1"6print(f"从第 {level} 关开始")7 8open("lamp.txt")
从第 1 关开始
Traceback (most recent call last):
File "main.py", line 8, in <module>
open("lamp.txt")
FileNotFoundError: [Errno 2] No such file or directory: 'lamp.txt'发生了什么: 程序在这里停下来了。
最后一行外面没有 try,文件名拼错了,程序就停了。报错里写着它找的是哪个文件,先查这两样:名字拼对了没有,文件在不在程序运行的那个文件夹里。
读写数字
文件里存的是文字,所以 write 只收字符串,f.write(12) 是 TypeError。写进去的时候用 str() 变成文字,读出来再用 int() 或 float() 变回数字:
1readings = [12, 7, 30]2with open("meter.txt", "w") as f:3 for r in readings:4 f.write(str(r) + "\n")5 6total = 07with open("meter.txt") as f:8 for line in f:9 total += int(line)10print(f"总数:{total}")
总数:49
int() 不管数字两边的空格和 \n,所以 int("12\n") 就是 12。不过空行还是会 ValueError。
CSV 文件
CSV(逗号分隔值)文件是用纯文字写的表格:一行一条记录,列和列之间用逗号隔开,第一行常常是列名。电子表格软件能打开,也能存成这种格式。文件夹里也有一个:
name,count 矿石,12 灯油,5 灯芯,40 玻璃,3
csv 模块替你把每行拆开。csv.reader 把每行变成一个列表;csv.DictReader 把第一行当作名字,把每行变成一个字典:
1import csv2 3with open("stock.csv", newline="") as f:4 for row in csv.reader(f):5 print(row)6 7with open("stock.csv", newline="") as f:8 for row in csv.DictReader(f):9 if int(row["count"]) < 10:10 print("要补货:" + row["name"])
['name', 'count'] ['矿石', '12'] ['灯油', '5'] ['灯芯', '40'] ['玻璃', '3'] 要补货:灯油 要补货:玻璃
读出来的值都是字符串,所以比大小之前要 int()。打开 CSV 文件时写上 newline="",换行交给 csv 模块自己处理。写反过来就行:csv.writer 写列表,csv.DictWriter 写字典。
1import csv2 3orders = [{"name": "灯油", "count": 20}, {"name": "玻璃", "count": 10}]4with open("orders.csv", "w", newline="") as f:5 writer = csv.DictWriter(f, fieldnames=["name", "count"])6 writer.writeheader()7 writer.writerows(orders)8 9with open("orders.csv") as f:10 print(f.read(), end="")
name,count 灯油,20 玻璃,10
JSON
JSON 是一种存嵌套数据的文字格式:字典、列表、字符串、数字、True/False 和 None(JSON 里写作 true、false 和 null)。很多程序和网站都用它交换数据。json.dump 把一个值写进文件,json.load 读回来,类型都不变(大体如此:元组读回来会变成列表);dumps 和 loads(多一个 s,表示字符串)对字符串做同样的事。
1import json2 3town = {"name": "像素小镇", "lamps": 4, "streets": ["榆树街", "橡树巷"], "lit": True}4with open("town.json", "w") as f:5 json.dump(town, f, ensure_ascii=False)6 7with open("town.json") as f:8 loaded = json.load(f)9print(loaded["streets"])10print(loaded == town)11 12text = json.dumps(town, ensure_ascii=False)13print(text)
['榆树街', '橡树巷']
True
{"name": "像素小镇", "lamps": 4, "streets": ["榆树街", "橡树巷"], "lit": true}
和 CSV 不一样,数字读回来还是数字,列表还是列表。JSON 默认把汉字写成 \u50cf 这样的编码,ensure_ascii=False 让汉字原样写出来。json.dumps(town, indent=2) 把文字分成好几行,更好读。
练一练
写好代码,点检查。提示随便看,实在想不出来再看答案。每道题的文件夹里一开始都有 lamps.txt 和 stock.csv。
数一数有几条街
✓读 lamps.txt,显示里面一共有几条街。
4 条街
1count = 02# 在这里读 lamps.txt3print(f"{count} 条街")
再加一条街
✓在 lamps.txt 末尾追加一行 采石场路,然后读这个文件,给每条街带上编号显示出来。
1 榆树街 2 橡树巷 3 磨坊路 4 港湾路 5 采石场路
1with open("lamps.txt") as f:2 for line in f:3 print(line.strip())
库存一共多少
✓用 csv.DictReader 读 stock.csv,把 count 这一列加起来。
总数:60
1import csv2 3total = 04print(f"总数:{total}")
这一课学会了
open(名字, 模式)打开文件;with open(...) as f:替你关上文件,出了错也会关。read()读全部,readline()读一行,readlines()读成列表;for line in f:一行一行读,每行末尾带着\n。"w"先清空再写,"a"加在末尾,"r"读;文件不存在会抛出FileNotFoundError。- 文件里存的是文字:数字写进去用
str(),读出来用int()或float()。 csv读写表格(reader、writer、DictReader、DictWriter);json存嵌套数据(dump、load、dumps、loads)。