Python 异常处理与自定义异常

AI 辅助生成本文由 AI 起草并经人工核查,示例均标明软件版本;发现错误请以官方文档为准。

Python 3.13

文件不存在、网络断开、数据格式不对——异常处理的目的是让程序在这些情况下给出明确反馈,而不是崩在一处看不懂的栈里。

环境准备

示例在 Python 3.13 下验证,无需额外依赖。

try / except / else / finally

try:
    value = int(input("输入数字:"))
except ValueError:
    print("这不是数字")
else:
    print(f"收到 {value}")     # 没抛异常才执行
finally:
    print("收尾工作")           # 无论是否异常都执行

执行顺序:

  1. try 块正常结束 → else → finally
  2. try 抛异常且被捕获 → 对应 except → finally
  3. try 抛异常且没被捕获 → 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,项目内建一个基类。

下一步