# 除以零
1 / 0--------------------------------------------------------------------------- ZeroDivisionError Traceback (most recent call last) Cell In[1], line 2 1 # 除以零 ----> 2 1 / 0 ZeroDivisionError: division by zero
写代码时,出错是常有的事。
有些错误是语法错误——代码还没跑起来,Python 就告诉你哪里写错了,比如漏了冒号、括号不匹配。这类错误很好修,看一眼就知道。
更多的错误发生在运行时:用户输入了意料之外的数、要读的文件不存在、列表索引越界……这些错误在 Python 中统称为异常(exception)。
本章学习如何让程序在遇到异常时不直接崩溃,而是优雅地处理。
先来看几种你很可能已经见过的异常:
# 除以零
1 / 0--------------------------------------------------------------------------- ZeroDivisionError Traceback (most recent call last) Cell In[1], line 2 1 # 除以零 ----> 2 1 / 0 ZeroDivisionError: division by zero
# 类型正确,但值不合适
int('abc')--------------------------------------------------------------------------- ValueError Traceback (most recent call last) Cell In[2], line 2 1 # 类型正确,但值不合适 ----> 2 int('abc') ValueError: invalid literal for int() with base 10: 'abc'
# 列表索引越界
[1, 2, 3][10]--------------------------------------------------------------------------- IndexError Traceback (most recent call last) Cell In[3], line 2 1 # 列表索引越界 ----> 2 [1, 2, 3][10] IndexError: list index out of range
# 字典键不存在
{'a': 1}['b']--------------------------------------------------------------------------- KeyError Traceback (most recent call last) Cell In[4], line 2 1 # 字典键不存在 ----> 2 {'a': 1}['b'] KeyError: 'b'
# 使用未定义的变量
print(x)--------------------------------------------------------------------------- NameError Traceback (most recent call last) Cell In[5], line 2 1 # 使用未定义的变量 ----> 2 print(x) NameError: name 'x' is not defined
每种异常都有自己的名字——ZeroDivisionError、ValueError、IndexError 等。看到报错时,最后一行的开头就是异常名。
下面是编写 Python 程序时最常见的异常:
| 异常名 | 含义 | 典型触发场景 |
|---|---|---|
ValueError |
操作或函数收到类型正确但值不合适的参数 | int("abc")、转换输入数据 |
ZeroDivisionError |
除数为零 | 1 / 0 |
IndexError |
序列索引超出范围 | [1,2,3][10] |
KeyError |
字典键不存在 | {"a": 1}["b"] |
FileNotFoundError |
文件不存在 | open('不存在的文件.txt') |
TypeError |
对不支持该操作的类型进行操作 | 'hello' + 5 |
NameError |
使用了未定义的变量 | print(x) 但 x 未定义 |
上一章学过:Python 中一切都是对象。异常也不例外——ValueError、ZeroDivisionError 等都是类,它们抛出的异常实例就是这些类的实例。
所有内置异常都继承自 Exception 类。因此编写 except Exception: 可以捕获几乎所有的常见异常。
如果一段代码可能抛出异常,可以用 try-except 捕获它,而不是让程序直接崩溃。
try:
# 可能出错的代码
except 异常类型:
# 出错时执行的代码当 try 块中的代码抛出了指定类型的异常时,程序会跳过 try 块中剩余的代码,直接进入对应的 except 块执行。如果 try 块没有抛出异常,except 块被跳过。
# 没有异常处理的版本:输入 'abc' 直接崩溃
x = int(input('请输入一个数字:'))# 有异常处理的版本:输入 'abc' 也不会崩溃
try:
x = int(input('请输入一个数字:'))
except ValueError:
print('输入无效,请确保输入的是数字')同一个 try 后面可以跟多个 except,分别处理不同类型的异常:
try:
data = input('请输入除数:')
num = int(data)
result = 100 / num
except ValueError:
print('输入的不是有效数字')
except ZeroDivisionError:
print('除数不能为零')如果多个异常的处理方式相同,也可以写在一起:
try:
data = input('请输入除数:')
num = int(data)
result = 100 / num
except (ValueError, ZeroDivisionError):
print('输入有误,请检查')有时不仅需要捕获异常,还想拿到异常对象本身查看详细信息。使用 as 关键字:
try:
1 / 0
except ZeroDivisionError as e:
print(f'发生了异常:{e}')发生了异常:division by zero
e 就是捕获到的异常实例,打印它可以看到异常的具体描述。
try-except 还有两个可选块:else 和 finally。
else:try 块没有抛出异常时执行。 用于放置正常情况下要继续的代码,避免把它们塞在 try 块里被误捕获。
try:
data = int(input('请输入数字:'))
except ValueError:
print('输入无效')
else:
# 只有没发生异常时才执行
print(f'有效输入:{data}')finally:无论是否发生异常都会执行。 适合放置”收尾”代码,比如关闭文件、释放资源等。
try:
data = int(input('请输入数字:'))
except ValueError:
print('输入无效')
else:
print(f'有效输入:{data}')
finally:
print('程序执行完毕')即使 try 或 except 中包含 return 语句,finally 也会在函数真正返回之前执行。这确保了资源一定能被释放,是文件操作等场景中的标准模式。
# 具有try, except, else, finally的代码的执行逻辑
# 伪代码
执行'try'语句块
if 执行'try'语句块时出现异常:
执行'except'块
else:
执行'else'块
执行'finally'块try-except 是接收异常。反过来,你也可以用 raise 关键字主动抛出异常。
什么时候需要自己抛异常?看一个例子:
def withdraw(balance, amount):
if amount > balance:
return -1 # 用特殊返回值表示错误
return balance - amount
# 调用者很容易忘记检查返回值
result = withdraw(100, 200)
# 拿着 -1 继续算,结果越来越离谱用 raise 改写:
def withdraw(balance, amount):
if amount > balance:
raise ValueError('余额不足')
return balance - amount
# 调用时如果不处理,程序会直接报错——迫使调用者处理
try:
result = withdraw(100, 200)
except ValueError as e:
print(e)余额不足
raise 和 return 的区别:
return:正常返回一个值,调用方继续执行raise:中断当前函数,异常沿着调用栈向上传播。如果一路上都没有 try-except 捕获,程序就崩溃raise 让函数更”诚实”——遇到处理不了的情况就直接报告,而不是返回一个容易被人忽略的特殊值。
常见的用法是在函数开头校验参数:
def set_score(score):
if not 0 <= score <= 100:
raise ValueError('成绩必须在 0 到 100 之间')
print(f'设置成绩:{score}')data = ["10", "20", "三十", "40"]
total = 0
for item in data:
total += int(item) safe_divide(a, b),要求:
b 为 0,抛出 ZeroDivisionError(用 raise)a 或 b 不是数字(int 或 float),抛出 TypeErrora / b