Python 异常处理与自定义异常
AI 辅助生成本文由 AI 起草并经人工核查,示例均标明软件版本;发现错误请以官方文档为准。
文件不存在、网络断开、数据格式不对——异常处理的目的是让程序在这些情况下给出明确反馈,而不是崩在一处看不懂的栈里。
环境准备
示例在 Python 3.13 下验证,无需额外依赖。
try / except / else / finally
try:
value = int(input("输入数字:"))
except ValueError:
print("这不是数字")
else:
print(f"收到 {value}") # 没抛异常才执行
finally:
print("收尾工作") # 无论是否异常都执行
执行顺序:
try块正常结束 →else→finallytry抛异常且被捕获 → 对应except→finallytry抛异常且没被捕获 →finally→ 异常继续向外传播
finally 用于释放资源(关文件、断开连接);有了 with 之后,大部分场景用不上它。
捕获具体的异常
try:
with open("config.json", encoding="utf-8") as f:
config = json.load(f)
except FileNotFoundError:
print("配置文件不存在,使用默认值")
config = {}
except json.JSONDecodeError as e:
print(f"配置文件格式错误:{e}")
raise # 无法恢复,重新抛出
要点:
- 捕获你能处理的异常,不要写
except Exception把所有问题都吞掉。 - 多个
except从具体到宽泛,ValueError要写在Exception前面。 as e拿到异常对象,str(e)是错误信息。- 只想记录后再抛出,用不带参数的
raise,保留原始栈。
else 块的价值:把「可能出错的代码」限制在 try 里,避免误捕后续代码的异常。
主动抛出异常
def divide(a, b):
if b == 0:
raise ValueError("除数不能为 0")
return a / b
参数校验用内置异常类型就够了:
| 场景 | 异常 |
|---|---|
| 值不合适 | ValueError |
| 类型不对 | TypeError |
| 键不存在 | KeyError |
| 下标越界 | IndexError |
| 文件不存在 | FileNotFoundError |
异常链:raise … from
捕获后转成另一个异常抛出时,用 from 保留原因:
try:
config = load_config()
except OSError as e:
raise ConfigError("配置加载失败") from e
输出会显示两段:
ConfigError: 配置加载失败
...
The above exception was the direct cause of the following exception:
FileNotFoundError: [Errno 2] No such file ...
想隐藏原始异常(一般不建议)用 from None。
自定义异常
业务错误应该有名字,方便调用方区分处理:
class AppError(Exception):
"""应用异常的基类。"""
class ConfigError(AppError):
pass
class ValidationError(AppError):
def __init__(self, field: str, message: str):
super().__init__(f"{field}: {message}")
self.field = field
用法:
try:
save(user)
except ValidationError as e:
print(f"字段 {e.field} 校验失败:{e}")
except AppError:
print("其他应用错误")
约定:
- 继承
Exception(不是BaseException,后者包含KeyboardInterrupt这类系统退出信号)。 - 定义一个项目基类,其他异常继承它,调用方可以一次性捕获。
- 异常名以
Error结尾。
finally 与 return 的冲突
def bad():
try:
return "try"
finally:
return "finally" # 覆盖掉 try 的返回值,且会吞掉异常
finally 里的 return 会丢掉 try 的返回值,还会吞掉未处理的异常。不要在 finally 里 return。
常见错误与注意事项
1. 裸 except 或 except Exception: pass
try:
risky()
except Exception:
pass # 什么都看不到,问题被彻底隐藏
至少记录日志:
import logging
try:
risky()
except Exception:
logging.exception("risky 失败") # 自动带上栈
raise
2. 用异常做正常流程控制
try:
value = d["key"]
except KeyError:
value = None
能直接判断就用 API:value = d.get("key")。
3. 捕获顺序写反
except Exception 写在最前面,后面的 except ValueError 永远不会执行(Python 会报语法警告)。
4. 在循环里捕获过宽的异常
一次捕获把整批数据的错误都吞了,很难定位。要么精确定位,要么记录下标后继续。
小结
- 只捕获能处理的异常,具体类型写在前面。
else放「确定不会出错」的后续逻辑,finally做清理且不要return。- 转换异常用
raise ... from e保留原因。 - 自定义异常继承
Exception,项目内建一个基类。