文件与异常处理
引入| English | 释义 | 拼音 |
|---|---|---|
| exception/ekˈsepʃn/ | 异常 | yì cháng |
为什么游戏对新玩家会崩溃?
- 某个游戏从
settings.txt读取玩家的文字大小。在开发者的电脑上一切正常。新玩家第一次运行时还没有这个文件,游戏就因FileNotFoundError停止了。 - 程序要面对一个混乱的世界:文件不存在、输入打错、数据有误。这节课讲怎样读写文件,以及怎样为可以预见的错误做好准备。
读取文件
with open("scores.txt") as f:打开文件,并在代码块结束时替你关闭它,即使块内发生了错误。- 用
for line in f:循环,每次得到一行,末尾带着"\n"。拆分或转换之前,先调用line.strip()。
total = 0
with open("scores.txt") as f: # Ana,72 Ben,55 Cai,90
for line in f:
name, mark = line.strip().split(",")
total += int(mark)
print(total) # 217
一个文件有三行:Ana,72、Ben,55、Cai,90。循环对每一行加上 int(line.strip().split(",")[1])。总数是多少?
难度 1/5 · 今日首次完成可获 100 金币及少量额外奖励
$72 + 55 + 90 = 217$。strip() 去掉 "\n",split(",") 把名字和分数分开,[1] 取出分数。
写入文件
open(path, "w")先清空文件,再写入。open(path, "a")追加到末尾,保留原有内容。f.write()接受一个字符串,并且不会自动换行,所以要写f.write(name + "\n")或f.write(f"{name},{mark}\n")。
把每种模式与它的作用配对。
难度 2/5 · 今日首次完成可获 140 金币及少量额外奖励
"r" 是默认模式。本想追加日志却用了 "w",每次运行都会把它抹掉。
错误都有名字
- 出问题时,Python 会引发一个异常(exception),每种异常都有名字:
FileNotFoundError(没有这个文件)、ValueError(int("x"))、KeyError(字典里没有这个键)、TypeError("a" + 1)、ZeroDivisionError(1 / 0)。 - 名字是错误信息中最有用的一行。先读它,再看它上面的行号。
把每一行与它引发的异常配对。
难度 2/5 · 今日首次完成可获 140 金币及少量额外奖励
ValueError 是类型对但值不对;TypeError 是该运算用了错误的类型。
try 与 except
- 把有风险的行放进
try:代码块,把针对某个具名错误的处理放进except ValueError:代码块。如果发生这个错误,Python 直接跳到except代码块。 try块中出错行之后的行会被跳过。整个try … except之后的代码照常运行。
try:
print("A")
n = int("seven") # ValueError here
print("B") # skipped
except ValueError:
print("C")
print("D") # prints A, C, D
A/B/C/D 程序打印什么?try: 打印 A,运行 int("seven"),打印 B;except ValueError: 打印 C;代码块之后打印 D。
难度 1/5 · 今日首次完成可获 100 金币及少量额外奖励
先打印 A,然后 int("seven") 引发异常。try 块的其余部分(包括 B)被跳过;C 运行;D 在整个代码块之后,所以也会运行。
一直询问,直到输入有效
- 把
try … except放进while True:循环。有效的值到达return,同时离开循环和函数。无效的值被捕获,循环再问一次。 - 用户先输入
x再输入4,会看到一条提示,然后程序用4继续运行。
def read_count():
while True:
try:
return int(input("How many? "))
except ValueError:
print("Whole numbers only.")
用户在 read_count() 中依次输入 x、2.5、4。"Whole numbers only." 打印了几次?
难度 2/5 · 今日首次完成可获 140 金币及少量额外奖励
int("x") 和 int("2.5") 都引发 ValueError,所以提示出现两次。int("4") 成功,return 结束循环。
一个结果,几条路
- 健壮的代码先尝试正常路径,为每种预料到的失败命名,并给每种失败一个合理的后备方案。调用者总能得到可用的结果。
- 只捕获你有对策的错误。你自己代码里的笔误仍然应该大声崩溃,这样你才能找到它。

- 从
settings.txt读取文字大小;如果文件不存在或内容不是整数,就用 12。 try:打开文件,size = int(f.read().strip())。except FileNotFoundError: size = 12和except ValueError: size = 12。- 文件内容为
16时得到 16;没有文件得到 12;文件内容为big时得到 12。新玩家不会再看到崩溃。
把设置读取程序的各行按顺序排列。
难度 3/5 · 今日首次完成可获 200 金币及少量额外奖励
有风险的打开和转换放在 try 中。两种错误使用同一个后备方案时,一个 except 可以用元组写出两个错误名。
容易丢分的地方
- 不带名字的
except:。 它捕获所有错误,连你自己的 bug 也一起藏起来。要写出你预料的错误名。 - 太大的
try块。 只把有风险的行放进去,这样你才知道except在应对哪一行。 - 该用
"a"却用了"w"。"w"会先抹掉原有内容。 - 忘了
strip()和"\n"。 读入的行保留换行符;写出的行需要加上换行符。
不带名字的 except: 是最安全的选择,因为它让程序永远不会崩溃。
难度 1/5 · 今日首次完成可获 100 金币及少量额外奖励
它也会吞掉你自己的 bug,例如拼错的变量名,使它们悄无声息地失败。要写出你预料并能应对的错误名。
你掌握了
with open(...)会替你关闭文件。"r"读取,"w"清空后写入,"a"追加;读入的行要 strip,写出的行要加"\n"。- 异常有名字。
try … except 名字:只捕获那一种错误;try块的其余部分会被跳过。 - 只捕获你能应对的错误,为每种错误准备后备方案,让意外的错误崩溃,这样你才能修复它们。