用户工具

站点工具


python:第九章异常处理

第九章 异常处理

本章概要

异常处理是编写健壮程序的关键。本章将学习Python的异常类型、异常处理机制和自定义异常的创建。

9.1 异常基础

9.1.1 什么是异常

异常是程序执行过程中发生的错误事件,会中断正常的程序流程。

# 常见异常示例
print(1 / 0)              # ZeroDivisionError
int("abc")                # ValueError
"hello"[10]               # IndexError
{"a": 1}["b"]             # KeyError
open("nonexistent.txt")   # FileNotFoundError

9.1.2 异常处理结构

try:
    # 可能引发异常的代码
    result = 10 / 0
except ZeroDivisionError:
    # 处理特定异常
    print("不能除以零")
except Exception as e:
    # 处理其他异常
    print(f"发生错误: {e}")
else:
    # 没有异常时执行
    print(f"结果是: {result}")
finally:
    # 无论是否异常都执行
    print("清理工作")

9.2 异常类型

9.2.1 内置异常层次结构

BaseException
├── SystemExit              # sys.exit()
├── KeyboardInterrupt       # Ctrl+C
├── GeneratorExit           # 生成器关闭
└── Exception               # 所有内置异常的基类
    ├── ArithmeticError
    │   ├── ZeroDivisionError
    │   └── OverflowError
    ├── LookupError
    │   ├── IndexError
    │   └── KeyError
    ├── TypeError
    ├── ValueError
    ├── AttributeError
    ├── NameError
    │   └── UnboundLocalError
    ├── FileNotFoundError
    ├── PermissionError
    ├── OSError
    └── ...

9.2.2 常见异常详解

# ZeroDivisionError - 除零错误
try:
    x = 1 / 0
except ZeroDivisionError as e:
    print(f"错误: {e}")
 
# ValueError - 值错误
try:
    x = int("not a number")
except ValueError as e:
    print(f"错误: {e}")
 
# TypeError - 类型错误
try:
    x = "2" + 2
except TypeError as e:
    print(f"错误: {e}")
 
# KeyError - 键错误
try:
    d = {"a": 1}
    x = d["b"]
except KeyError as e:
    print(f"键不存在: {e}")
 
# IndexError - 索引错误
try:
    lst = [1, 2, 3]
    x = lst[10]
except IndexError as e:
    print(f"索引越界: {e}")
 
# AttributeError - 属性错误
try:
    x = 10
    x.append(5)
except AttributeError as e:
    print(f"属性错误: {e}")

9.3 异常处理技巧

9.3.1 捕获多个异常

# 方式1:分别处理
try:
    # 可能引发多种异常的代码
    pass
except ValueError as e:
    print(f"值错误: {e}")
except TypeError as e:
    print(f"类型错误: {e}")
 
# 方式2:统一处理
try:
    pass
except (ValueError, TypeError) as e:
    print(f"输入错误: {e}")
 
# 方式3:捕获所有异常(不推荐)
try:
    pass
except Exception as e:
    print(f"发生错误: {e}")

9.3.2 异常信息获取

import traceback
 
try:
    result = 10 / 0
except Exception as e:
    print(f"异常类型: {type(e).__name__}")
    print(f"异常信息: {e}")
    print(f"异常参数: {e.args}")
    print("完整堆栈:")
    traceback.print_exc()

9.3.3 异常链

def read_file(filename):
    try:
        with open(filename, 'r') as f:
            return f.read()
    except FileNotFoundError as e:
        # 保留原始异常信息
        raise RuntimeError(f"无法读取文件: {filename}") from e
 
try:
    content = read_file("missing.txt")
except RuntimeError as e:
    print(f"错误: {e}")
    print(f"原因: {e.__cause__}")
 
# 不使用from(隐式链)
try:
    int("abc")
except ValueError:
    raise RuntimeError("转换失败")  # __context__保留原始异常
 
# 完全断开链
# raise RuntimeError("新错误") from None

9.4 自定义异常

9.4.1 创建自定义异常

class ValidationError(Exception):
    """验证错误基类"""
    pass
 
class ValueTooSmallError(ValidationError):
    """值太小错误"""
    def __init__(self, value, min_value):
        self.value = value
        self.min_value = min_value
        super().__init__(f"值 {value} 小于最小值 {min_value}")
 
class ValueTooLargeError(ValidationError):
    """值太大错误"""
    def __init__(self, value, max_value):
        self.value = value
        self.max_value = max_value
        super().__init__(f"值 {value} 大于最大值 {max_value}")
 
# 使用
def validate_number(value, min_val=0, max_val=100):
    if value < min_val:
        raise ValueTooSmallError(value, min_val)
    if value > max_val:
        raise ValueTooLargeError(value, max_val)
    return value
 
try:
    validate_number(150)
except ValidationError as e:
    print(f"验证失败: {e}")

9.4.2 业务异常示例

class BusinessError(Exception):
    """业务错误基类"""
    def __init__(self, message, code=None):
        super().__init__(message)
        self.code = code
 
class InsufficientFundsError(BusinessError):
    """余额不足"""
    def __init__(self, balance, amount):
        self.balance = balance
        self.amount = amount
        super().__init__(
            f"余额不足: 当前余额 {balance}, 需要 {amount}",
            code="E001"
        )
 
class AccountNotFoundError(BusinessError):
    """账户不存在"""
    def __init__(self, account_id):
        self.account_id = account_id
        super().__init__(
            f"账户不存在: {account_id}",
            code="E002"
        )
 
class BankAccount:
    def __init__(self, account_id, balance=0):
        self.account_id = account_id
        self.balance = balance
 
    def withdraw(self, amount):
        if amount > self.balance:
            raise InsufficientFundsError(self.balance, amount)
        self.balance -= amount
        return self.balance
 
# 使用
account = BankAccount("123", 100)
try:
    account.withdraw(200)
except InsufficientFundsError as e:
    print(f"[{e.code}] {e}")
    print(f"当前余额: {e.balance}")

9.5 断言(Assertion)

def divide(a, b):
    assert b != 0, "除数不能为零"
    assert isinstance(a, (int, float)), "a必须是数字"
    assert isinstance(b, (int, float)), "b必须是数字"
    return a / b
 
# 断言只在调试模式下有效
# 使用 -O 参数运行Python时,所有assert语句被忽略

9.6 上下文管理器中的异常

class DatabaseConnection:
    def __enter__(self):
        print("连接数据库")
        return self
 
    def __exit__(self, exc_type, exc_val, exc_tb):
        """
        返回True: 抑制异常
        返回False: 传播异常
        """
        print("关闭数据库连接")
        if exc_type is not None:
            print(f"发生异常: {exc_type.__name__}: {exc_val}")
            # 处理特定异常
            if exc_type is ConnectionError:
                print("连接错误已处理")
                return True  # 抑制异常
        return False  # 传播其他异常
 
# 使用
with DatabaseConnection() as db:
    # raise ConnectionError("连接失败")
    pass

9.7 最佳实践

9.7.1 异常处理原则

# 1. 捕获具体的异常,而不是所有异常
# 不推荐
try:
    pass
except Exception:  # 太宽泛
    pass
 
# 推荐
try:
    pass
except ValueError:
    pass
 
# 2. 不要捕获异常后什么都不做
# 不推荐
try:
    do_something()
except ValueError:
    pass  # 静默失败
 
# 推荐
try:
    do_something()
except ValueError as e:
    logger.warning(f"操作失败: {e}")
 
# 3. 使用finally或上下文管理器确保资源释放
# 推荐
with open('file.txt') as f:
    process(f)
 
# 4. 尽早失败,快速报错
def process_data(data):
    if not data:
        raise ValueError("数据不能为空")
    if not isinstance(data, dict):
        raise TypeError("数据必须是字典")
    # 处理数据

9.7.2 异常与日志

import logging
 
logging.basicConfig(
    level=logging.ERROR,
    format='%(asctime)s - %(levelname)s - %(message)s',
    filename='errors.log'
)
 
def safe_divide(a, b):
    try:
        return a / b
    except ZeroDivisionError as e:
        logging.error(f"除零错误: a={a}, b={b}", exc_info=True)
        raise
 
# 使用
# safe_divide(10, 0)

9.8 代码示例

示例1:重试装饰器

import time
import functools
 
def retry(max_attempts=3, delay=1, exceptions=(Exception,)):
    """重试装饰器"""
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(1, max_attempts + 1):
                try:
                    return func(*args, **kwargs)
                except exceptions as e:
                    if attempt == max_attempts:
                        raise
                    print(f"尝试 {attempt} 失败: {e},{delay}秒后重试...")
                    time.sleep(delay)
        return wrapper
    return decorator
 
@retry(max_attempts=3, delay=1, exceptions=(ConnectionError,))
def connect_to_server():
    import random
    if random.random() < 0.7:
        raise ConnectionError("连接失败")
    return "连接成功"
 
# 使用
# result = connect_to_server()
# print(result)

示例2:参数验证装饰器

from functools import wraps
 
def validate_types(**types):
    """类型验证装饰器"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            # 获取参数名和值的对应
            func_code = func.__code__
            arg_names = func_code.co_varnames[:func_code.co_argcount]
            all_args = dict(zip(arg_names, args))
            all_args.update(kwargs)
 
            # 验证类型
            for name, expected_type in types.items():
                if name in all_args:
                    value = all_args[name]
                    if not isinstance(value, expected_type):
                        raise TypeError(
                            f"参数 '{name}' 必须是 {expected_type.__name__},"
                            f"实际是 {type(value).__name__}"
                        )
 
            return func(*args, **kwargs)
        return wrapper
    return decorator
 
@validate_types(name=str, age=int)
def greet(name, age):
    return f"Hello, {name}! You are {age} years old."
 
# 测试
print(greet("Alice", 25))  # 正常
# print(greet("Bob", "25"))  # TypeError

9.9 练习题

练习1:安全的除法函数

def safe_divide(a, b, default=None):
    """安全的除法,处理各种异常情况"""
    try:
        result = float(a) / float(b)
        return result
    except ZeroDivisionError:
        print("警告: 除数为零")
        return default
    except (ValueError, TypeError):
        print("警告: 无效的输入类型")
        return default
    except Exception as e:
        print(f"未知错误: {e}")
        return default
 
# 测试
print(safe_divide(10, 2))      # 5.0
print(safe_divide(10, 0))      # None
print(safe_divide("10", "2"))  # 5.0
print(safe_divide("abc", 2))   # None

练习2:自定义上下文管理器

from contextlib import contextmanager
 
@contextmanager
def ignore_exception(*exceptions):
    """忽略指定异常的上下文管理器"""
    try:
        yield
    except exceptions as e:
        print(f"忽略异常: {type(e).__name__}: {e}")
 
# 使用
with ignore_exception(ZeroDivisionError, ValueError):
    print(1 / 0)  # 被忽略
 
with ignore_exception(FileNotFoundError):
    with open("nonexistent.txt") as f:
        print(f.read())  # 被忽略

本章小结

本章学习了Python的异常处理:

  • 异常基础 - try-except-else-finally结构
  • 异常类型 - 内置异常层次结构
  • 异常处理 - 多异常捕获、异常信息获取、异常链
  • 自定义异常 - 创建业务异常、异常最佳实践
  • 断言 - assert语句的使用
  • 最佳实践 - 异常处理原则和日志记录

良好的异常处理能让程序更加健壮和易于调试。

进一步阅读

python/第九章异常处理.txt · 最后更改: 127.0.0.1