错误和异常
有些代码不一定能执行成功。字符串里混进了字母,就不能转成整数:
print(int("567"))
print(int("56fdsa7"))
第一行得到 567。第二行程序停住,报错的最后一行是:
ValueError: invalid literal for int() with base 10: '56fdsa7'
不管这个错误,后面的代码就不会再执行。try / except 用来接住它,让程序换一条路继续走。
先把报错读完
单独运行上面第二行,完整报错是这样的:
Traceback (most recent call last):
File "convert.py", line 2, in <module>
print(int("56fdsa7"))
^^^^^^^^^^^^^^
ValueError: invalid literal for int() with base 10: '56fdsa7'
从下往上读:
- 最后一行是异常类型和说明。这里是
ValueError,意思是值的内容不合适 - 上面两行是出错的代码。
^^^^指着真正执行失败的那一段,也就是int("56fdsa7") File "convert.py", line 2是文件名和行号Traceback是调用过程。函数套函数时,这里会有好几层,最下面一层是直接出错的位置
语法错误在运行前就被发现,例如少写冒号、缩进对不齐,类型常常是 SyntaxError 或 IndentationError。这种错误写在同一个文件的 try 里面也接不住,因为文件还没开始执行。下面讲的是程序已经跑起来之后才出现的异常。
常见的几类
| 类型 | 什么时候出现 |
|---|---|
ValueError |
类型对,但值不合适,例如 int("56fdsa7") |
TypeError |
类型不能这样用,例如 1 + "3" |
NameError |
名字还没有定义 |
IndexError |
下标超出列表范围 |
KeyError |
字典里没有这个 key |
ZeroDivisionError |
除数是 0 |
FileNotFoundError |
要打开的文件不存在 |
各自的说明:
TypeError: unsupported operand type(s) for +: 'int' and 'str'
NameError: name 'no_such_name' is not defined
IndexError: list index out of range
KeyError: 'b'
ZeroDivisionError: division by zero
FileNotFoundError: [Errno 2] No such file or directory: 'no_such_file.txt'
KeyError: 'b' 表示字典里没有 "b" 这个 key。文件那一节的 open("no_such.txt") 报的就是 FileNotFoundError。怎么查这种报错,下一节 调试 会再顺着讲。
try / except
把可能出错的语句放进 try,出错时执行对应的 except:
try:
print(int("56fdsa7"))
except ValueError:
print("这不是一个整数")
这不是一个整数
程序不会退出。try 里如果没有出错,except 不会执行:
try:
print(int("567"))
except ValueError:
print("这不是一个整数")
567
except 后面写上具体类型。空的 except: 会把所有错误都吞掉,包括你打字打错造成的 NameError,排查时就像没报错一样。入门不要这样写。
要看原始说明,用 as 接住这个异常对象:
try:
print(int("56fdsa7"))
except ValueError as e:
print(type(e).__name__)
print(e)
ValueError
invalid literal for int() with base 10: '56fdsa7'
e 是这次出错的对象。type(e).__name__ 是类型名,直接打印 e 是冒号后面的那句说明。
同一种操作可能遇到不同的错,就写成多个 except,从上往下对上第一个就停:
def calc(text, divisor):
try:
n = int(text)
return n / divisor
except ValueError:
print("不是整数")
except ZeroDivisionError:
print("除数是 0")
print(calc("10", 2))
calc("56fdsa7", 2)
calc("10", 0)
5.0
不是整数
除数是 0
else 和 finally
try 也可以带 else 和 finally。else 只在 try 里没有出错时执行。finally 无论有没有出错都会执行,用来做收尾。
try:
n = int("567")
except ValueError:
print("这不是一个整数")
else:
print("转换成功", n)
finally:
print("收尾")
转换成功 567
收尾
转换失败时不会进 else,但 finally 仍会执行:
try:
n = int("56fdsa7")
except ValueError:
print("这不是一个整数")
else:
print("转换成功", n)
finally:
print("收尾")
这不是一个整数
收尾
打开文件后,无论中间有没有出错,都应该 close。可以放进 finally:
f = open("note.txt", "w", encoding="utf-8")
try:
f.write("hello")
finally:
f.close()
print(f.closed)
True
上一节的 with open(...) as f 就是把这件事写短了:离开 with 时一定会关闭,包括中途出错的情况。平时读写文件优先用 with。finally 留给别的必须收尾的动作。
自己抛出异常
发现数据不合法,可以用 raise 主动抛出异常,让调用的人用 try 去接:
def check_price(n):
if n < 0:
raise ValueError("价格不能是负数")
return n
print(check_price(10))
10
check_price(-1)
ValueError: 价格不能是负数
raise 后面跟一个异常对象。类型要和问题相符:值不合适用 ValueError,文件没有用 FileNotFoundError。不要为了省事一律 raise Exception。
常见坑
except: 什么都不写。 连拼写错误也会被当成“处理过了”。至少写成 except ValueError 这种具体类型。
父类型写在子类型前面。 FileNotFoundError 属于 OSError。先写 except OSError,后面的 except FileNotFoundError 永远走不到:
try:
open("no_such_file.txt", encoding="utf-8")
except OSError:
print("按 OSError 接住了")
except FileNotFoundError:
print("文件不存在")
按 OSError 接住了
更具体的类型要写在前面。只想处理文件不存在时,直接 except FileNotFoundError。
try 包得太大。 把十几行都放进一个 try,就看不出是哪一句出的错。只包真正可能失败的那几句,例如转换、除法、打开文件。
小结
- 报错从最后一行读起:类型、说明,再看行号和
^^^^指着的代码 try里出错才进入匹配的except,程序可以继续except 类型 as e能拿到这次错误的说明else在没出错时执行,finally总会执行- 关闭文件优先用
with,不必自己在finally里close raise用来主动报告不合法的数据- 不要写空的
except:,具体类型放在宽泛类型前面
下一节学习 随机数。用 python examples/error_demo.py 运行本节的例子。