====== 第九章 异常处理 ======
===== 本章概要 =====
异常处理是编写健壮程序的关键。本章将学习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|内置异常]]