异常处理
本教程共 70 篇 · 第 39 篇 · 更新于 2026-07-22 · 约 6 分钟阅读
39. 异常处理
本节目标:掌握
try/except/finally的完整结构,学会抛出异常和自定义异常类,理解异常链的用法。
程序运行时不一定事事顺利。文件可能不存在,网络可能超时,用户输入可能乱七八糟。这些「意外」在 Python 里叫「异常(exception)」。如果不处理,程序会崩溃并打印一堆红色的报错信息。
学会优雅地处理异常,是写出健壮代码的必修课。
异常的基本结构
最基础的异常处理:
try:
num = int(input("请输入一个数字:"))
result = 10 / num
print(f"结果是 {result}")
except ValueError:
print("输入的不是有效数字")
except ZeroDivisionError:
print("不能除以零")
try 块里放「可能出问题的代码」。如果发生异常,Python 会跳到对应的 except 块执行。如果没有异常,except 块被跳过。
Tip不要捕获太宽泛的异常。
except:或except Exception:会吞下所有错误,包括你没预料到的。尽量捕获具体的异常类型,让真正的问题暴露出来。
获取异常信息
如果需要异常的详细信息,用 as 捕获异常对象:
try:
with open('不存在的文件.txt', 'r') as f:
content = f.read()
except FileNotFoundError as e:
print(f"出错了:{e}")
print(f"异常类型:{type(e).__name__}")
多个 except 的匹配顺序
异常匹配从上到下,第一个匹配的 except 会被执行。所以要把具体的异常放前面,宽泛的放后面:
try:
# 某些操作
pass
except FileNotFoundError:
print("文件没找到")
except OSError:
print("操作系统错误(包括文件没找到)")
except Exception:
print("其他未知错误")
如果 FileNotFoundError 和 OSError 顺序反过来,FileNotFoundError 永远不会被触发,因为它是 OSError 的子类。
else 和 finally
try 后面还可以跟 else 和 finally:
try:
f = open('data.txt', 'r')
except FileNotFoundError:
print('文件不存在')
else:
# try 块没异常时才执行
content = f.read()
print(content)
f.close()
finally:
# 无论有没有异常,都执行
print('清理工作完成')
else:try 成功执行后运行,适合放「依赖 try 结果但本身不会抛异常」的代码。finally:无论成功还是失败都会执行,适合放清理资源(关闭文件、断开连接)的代码。
Note如果
finally块里有return,它会覆盖try或except里的return。这是个容易踩的坑,尽量不在finally里写return。
用 with 语句简化资源管理
对于文件、锁、网络连接这类需要成对打开和关闭的资源,推荐用 with 语句。它会在结束时自动调用关闭方法,即使发生异常也能保证清理:
with open('data.txt', 'r') as f:
content = f.read()
# 离开 with 块,f 自动关闭
with 背后依赖的是「上下文管理器协议」(第 44 章会展开)。现在你只需要记住:凡是「打开后必须关闭」的资源,优先考虑 with。
主动抛出异常:raise
不只是等异常自己发生,你也可以主动抛出:
def withdraw(balance, amount):
if amount <= 0:
raise ValueError("取款金额必须大于零")
if amount > balance:
raise ValueError("余额不足")
return balance - amount
# withdraw(100, -10) # ValueError: 取款金额必须大于零
raise 后面跟异常类的实例。你可以用内建异常,也可以自定义。
自定义异常
当内建异常无法准确描述你的业务错误时,定义自己的异常类:
class BusinessError(Exception):
"""业务逻辑错误基类"""
pass
class InsufficientFundsError(BusinessError):
"""余额不足"""
def __init__(self, balance, amount):
self.balance = balance
self.amount = amount
super().__init__(f"余额 {balance},无法扣除 {amount}")
class InvalidOrderStateError(BusinessError):
"""订单状态非法"""
pass
def pay_order(balance, order_amount):
if balance < order_amount:
raise InsufficientFundsError(balance, order_amount)
return balance - order_amount
# pay_order(100, 200)
# InsufficientFundsError: 余额 100,无法扣除 200
自定义异常通常继承 Exception(或它的子类),类名以 Error 结尾。这样调用方可以用 except BusinessError 统一捕获你的业务异常,而不影响系统级别的错误。
异常链:from
有时候你在处理一个异常时,又抛出了新的异常。用 from 保留原始异常的信息,方便追踪根因:
import json
def parse_config(path):
try:
with open(path, 'r') as f:
return json.load(f)
except FileNotFoundError as e:
raise ConfigError(f"找不到配置文件:{path}") from e
except json.JSONDecodeError as e:
raise ConfigError(f"配置文件格式错误:{path}") from e
加上 from e 后, traceback 会显示完整的异常链:原始异常是什么,在哪个地方被转换成了新异常。
Warning不要滥用
raise ... from None。它会抹掉原始异常,只在极少数情况下(比如你想完全替换错误语义)才用。
异常处理的最佳实践
- 具体捕获:
except ValueError比except Exception好。 - 不要吞异常:空的
except:会让问题被隐藏,调试时生不如死。 - 异常是例外,不是流程控制:不要用异常替代正常的
if/else判断。 - 资源用
with管理:避免手动close()时忘记调用。
# 反例:用异常做流程控制
def find_user(users, user_id):
try:
return users[user_id]
except KeyError:
return None
# 正例:先判断再操作
def find_user(users, user_id):
return users.get(user_id) # 没有就返回 None,不用抛异常
小结
try/except捕获特定异常,as获取异常对象。else在 try 成功时执行,finally无论成败都执行。raise主动抛出异常,from保留异常链。- 自定义异常继承
Exception,按业务场景分层设计。 - 资源管理优先用
with,别靠finally手动关闭。
异常处理不是「把错误藏起来」,而是「在出错时给程序一条体面的退路」。好的异常处理让用户体验更好,也让排查问题更轻松。
来源:参考了 runoob「Python3 错误和异常」、liaoxuefeng「错误处理」、pythondoc「8. 错误和异常」等,改写后所得。