Python 教程 / 面向对象 / 错误处理

错误处理 (Exception Handling)

在程序运行过程中,错误是不可避免的。Python提供了强大的异常处理机制,让我们能够优雅地捕获和处理错误,避免程序崩溃,同时提供有用的错误信息。

什么是异常?

异常(Exception)是程序在执行过程中发生的特殊事件,通常表示出现了错误。当异常发生时,程序会停止执行并创建一个异常对象。

┌─────────────────────────────────────────────────────────────────┐
│                      正常执行 vs 异常处理                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   【没有异常处理】                                                │
│   ┌─────────────────────────────────────────────────────────┐  │
│   │  执行 line 1                                           │  │
│   │  执行 line 2                                           │  │
│   │  执行 line 3  ← 发生 ZeroDivisionError!                │  │
│   │  程序崩溃,停止执行                                      │  │
│   │  line 4, line 5 永远不会执行                           │  │
│   └─────────────────────────────────────────────────────────┘  │
│                                                                 │
│   【有异常处理】                                                  │
│   ┌─────────────────────────────────────────────────────────┐  │
│   │  try:                                                   │  │
│   │      执行 line 1                                       │  │
│   │      执行 line 2                                       │  │
│   │      执行 line 3  ← 发生异常!                         │  │
│   │      跳到 except 块                                    │  │
│   │  except:                                                │  │
│   │      处理异常                                           │  │
│   │  继续执行 line 4, line 5                              │  │
│   └─────────────────────────────────────────────────────────┘  │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
                         

常见异常类型

异常类型 说明 示例
ZeroDivisionError 除数为零 10 / 0
ValueError 值错误 int("abc")
TypeError 类型错误 "2" + 3
IndexError 索引越界 [1,2,3][10]
KeyError 键不存在 {"a":1}["b"]
FileNotFoundError 文件不存在 open("xxx.txt")
AttributeError 属性不存在 None.method()

try-except 捕获异常

使用 try-except 语句捕获可能发生异常的代码,并进行处理。

1. 基本语法

Python
try:
    # 可能发生异常的代码
    result = 10 / 0
    print(result)
except ZeroDivisionError:
    # 捕获 ZeroDivisionError 异常
    print("错误:不能除以零!")

print("程序继续执行...") 

2. 捕获异常并获取信息

Python
try:
    num = int("abc")
except ValueError as e:
    # e 是异常对象,包含错误信息
    print(f"发生错误: {e}")
    print(f"异常类型: {type(e).__name}")

# 输出:
# 发生错误: invalid literal for int() with base 10: 'abc'
# 异常类型: ValueError 

3. 捕获多个异常

Python
# 方式1: 多个 except 子句(按顺序匹配)
try:
    result = int("abc")
except ZeroDivisionError:
    print("除数为零")
except ValueError:
    print("值错误")
except TypeError:
    print("类型错误")

# 方式2: 一个 except 捕获多种异常(元组)
try:
    result = 10 / "2"
except (ZeroDivisionError, TypeError, ValueError) as e:
    print(f"发生错误: {type(e).__name} - {e}") 

4. 捕获所有异常

Python
# 方式1: 捕获所有异常(不推荐,可能隐藏真正问题)
try:
    # 任何可能出错的代码
    pass
except Exception as e:
    print(f"发生错误: {e}")

# 方式2: 捕获所有异常(包括系统异常)
try:
    # 任何可能出错的代码
    pass
except BaseException as e:
    print(f"发生严重错误: {e}")

# 方式3: 空 except(最不推荐)
try:
    # 任何可能出错的代码
    pass
except:
    print("出错了") 
最佳实践
1. 尽量具体指定异常类型,而不是捕获所有异常
2. 避免空的 except: 子句,它会隐藏所有错误
3. 记录错误日志而不是仅仅打印
4. 考虑是否真的需要捕获异常,还是应该让程序失败并修复代码

else 和 finally 子句

┌─────────────────────────────────────────────────────────────────┐
│                   try-except-else-finally 结构                    │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   try:                                                          │
│       # 尝试执行的代码                                           │
│       # 可能抛出异常                                             │
│   except Exception as e:                                        │
│       # 捕获异常并处理                                          │
│   else:                                                         │
│       # try 块没有发生异常时执行                                 │
│   finally:                                                       │
│       # 无论是否发生异常,始终执行                               │
│       # 清理资源                                                 │
│                                                                 │
│   执行顺序:                                                      │
│   1. try 块正常执行 → else 块 → finally                        │
│   2. try 块异常 → except 块 → finally                          │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
                         
Python
try:
    num = int("123")
    result = num * 2
except ValueError as e:
    print(f"转换失败: {e}")
else:
    # 只有当 try 块没有发生异常时才会执行
    print(f"计算结果: {result}")
finally:
    # 无论是否发生异常,始终执行
    print("清理资源...")

# 输出:
# 计算结果: 246
# 清理资源... 

finally 的典型应用

Python
# 文件操作 - 确保资源释放
# 方式1: 手动关闭(容易遗漏)
f = open("test.txt", "r")
try:
    content = f.read()
finally:
    f.close()   # 确保关闭

# 方式2: with 语句(更简洁,推荐)
with open("test.txt", "r") as f:
    content = f.read()
# f 会自动关闭

# 数据库连接 - 确保连接释放
# conn = get_db_connection()
# try:
#     result = conn.execute("SELECT * FROM users")
# finally:
#     conn.close()  # 确保连接关闭 

raise 抛出异常

使用 raise 语句主动抛出异常。

1. 基本用法

Python
def divide(a, b):
    if b == 0:
        raise ZeroDivisionError("除数不能为零")
    return a / b

try:
    result = divide(10, 0)
except ZeroDivisionError as e:
    print(e)  # 除数不能为零 

2. raise 异常类

Python
# 直接抛出异常类(会自动创建实例)
raise ValueError("值错误信息")

# 抛出异常对象
raise ValueError("值错误信息")   # 带消息
raise ValueError()                 # 不带消息

# 重新抛出当前捕获的异常
try:
    # 某段可能出错的代码
    pass
except Exception as e:
    print(f"记录日志: {e}")
    raise  # 重新抛出,让调用者处理 

3. 异常链 (Python 3)

Python
# 显式异常链 - 在抛出新异常时记录原始异常
try:
    int("abc")
except ValueError as e:
    raise RuntimeError("转换失败") from e

# 隐式异常链 - raise 语句自动携带当前异常
try:
    int("abc")
except ValueError:
    raise RuntimeError("转换失败") # 隐式链接

# 禁用异常链
try:
    int("abc")
except ValueError as e:
    raise RuntimeError("转换失败") from None  # 断开链接 

自定义异常类

通过继承 Exception 创建自己的异常类。

┌─────────────────────────────────────────────────────────────────┐
│                      自定义异常类结构                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   class MyError(Exception):         ← 自定义异常继承 Exception   │
│       pass                                                  │
│                                                                 │
│   class ValidationError(Exception):  ← 验证错误                 │
│       def __init__(self, field, message):                    │
│           self.field = field                                  │
│           self.message = message                              │
│           super().__init__(f"{field}: {message}")             │
│                                                                 │
│   class PositiveOnlyError(ValueError):  ← 继承 ValueError      │
│       """只允许正数的异常"""                                     │
│       pass                                                    │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
                         
Python
# 基本自定义异常
class MyError(Exception):
    pass

raise MyError("这是一个错误")

# 带详细信息的异常
class ValidationError(Exception):
    def __init__(self, field, message):
        self.field = field
        self.message = message
        super().__init__(f"{field}: {message}")

raise ValidationError("age", "年龄必须是正数")

# 继承现有异常类
class PositiveOnlyError(ValueError):
    """数值必须为正的异常"""
    pass

def process_positive(num):
    if num <= 0:
        raise PositiveOnlyError(f"期望正数,实际得到 {num}")
    return num * 2

try:
    process_positive(-5)
except PositiveOnlyError as e:
    print(e) 

实际应用:业务异常体系

Python
# 业务异常体系
class AppError(Exception):
    """应用程序基础异常"""
    def __init__(self, message, code="APP_ERROR"):
        self.message = message
        self.code = code
        super().__init__(f"[{code}] {message}")

class ValidationError(AppError):
    """验证错误"""
    def __init__(self, field, message):
        super().__init__(f"字段 {field} 验证失败: {message}", "VALIDATION_ERROR")
        self.field = field

class AuthenticationError(AppError):
    """认证错误"""
    def __init__(self, message="认证失败"):
        super().__init__(message, "AUTH_ERROR")

# 使用
def login(username, password):
    if username != "admin":
        raise AuthenticationError("用户名不存在")
    if password != "123":
        raise AuthenticationError("密码错误")
    return "登录成功"

try:
    print(login("admin", "wrong"))
except AuthenticationError as e:
    print(f"错误码: {e.code}, 消息: {e.message}") 

最佳实践

异常处理原则
1. LBYL (Look Before You Leap) vs EAFP (Easier to Ask Forgiveness than Permission)
2. LBYL: 先检查再行动 - if key in dict: value = dict[key]
3. EAFP: 先行动后处理 - try: value = dict[key] except KeyError: ...
4. Python 社区更推荐 EAFP 风格
Python
# LBYL 风格 (先检查)
def get_user_age_lbyl(users, user_id):
    if user_id in users:
        return users[user_id]["age"]
    return None

# EAFP 风格 (先尝试) - 更 Pythonic
def get_user_age_eafp(users, user_id):
    try:
        return users[user_id]["age"]
    except KeyError:
        return None

# 文件操作的最佳实践
# 使用 with 语句自动管理资源
with open("data.txt", "r", encoding="utf-8") as f:
    content = f.read()
# 文件自动关闭,无需 finally

# 异常应该足够具体
# 不好: except Exception:
# 好: except ValueError as e: 

知识测验

问题1:以下代码的输出是什么?

try:
    print("A")
    raise ValueError("error")
    print("B")
except ValueError:
    print("C")
else:
    print("D")
finally:
    print("E") 

问题2:以下哪个是捕获所有异常的最佳方式?

课后练习

✏️ 练习1:实现安全的除法函数

实现一个安全的除法函数:

  • 接受两个参数 a 和 b
  • 如果 b 为零,抛出自定义异常 DivisionByZeroError
  • 如果 a 或 b 不是数字,抛出 TypeError
  • 返回 a / b 的结果
  • 调用者正确捕获并处理异常
✏️ 练习2:用户输入验证

实现用户注册验证:

  • 创建 ValidationError 异常类
  • 验证用户名(不能为空,长度3-20)
  • 验证密码(长度至少8位)
  • 验证邮箱格式
  • 收集所有验证错误,而不是遇到第一个就停止
✏️ 练习3:上下文管理器模拟

模拟数据库连接管理:

  • 创建 DatabaseConnection 异常
  • 使用 try-except-finally 模拟连接和断开
  • 在 finally 中确保连接被正确关闭
  • 处理可能的连接超时异常
🐍 Python 在线代码编辑器
📝 运行结果