====== 第九章 异常处理 ====== ===== 本章概要 ===== 异常处理是编写健壮程序的关键。本章将学习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语句的使用 * **最佳实践** - 异常处理原则和日志记录 良好的异常处理能让程序更加健壮和易于调试。 ===== 进一步阅读 ===== * [[https://docs.python.org/zh-cn/3/tutorial/errors.html|错误和异常教程]] * [[https://docs.python.org/zh-cn/3/library/exceptions.html|内置异常]]