一、什么是异常处理?
异常处理是 Python 中用于捕获和处理程序运行时错误的机制。当程序遇到错误时,它会"抛出"一个异常,如果不进行处理,程序就会终止。使用 try-except 可以优雅地处理这些错误,让程序能够继续运行或优雅地退出。
二、基本语法
1. try-except 基础结构
def basic_exception_handling(): """基本异常处理结构""" # 最简单的形式 try: result = 10 / 0 print(f"结果: {result}") except ZeroDivisionError: print("错误:除数不能为0") # 捕获异常并获取异常对象 try: result = 10 / 0 except ZeroDivisionError as e: print(f"捕获到异常: {e}") print(f"异常类型: {type(e).__name__}")basic_exception_handling()
2. 完整结构
def complete_exception_structure(): """完整的异常处理结构""" try: # 可能发生异常的代码 number = int(input("请输入一个数字: ")) result = 100 / number print(f"100 / {number} = {result}") except ValueError as e: # 处理值错误(输入不是数字) print(f"值错误: {e}") except ZeroDivisionError as e: # 处理除零错误 print(f"除零错误: {e}") except Exception as e: # 处理其他所有异常 print(f"未知错误: {e}") else: # 没有异常时执行 print("计算成功完成!") finally: # 无论是否发生异常都会执行 print("程序执行结束")# complete_exception_structure()
三、多种异常处理方式
1. 捕获多个异常
def multiple_exceptions(): """捕获多个异常的不同方式""" # 方式1:多个 except 块 def process_data1(data): try: result = int(data) result = 100 / result return result except ValueError: return "错误:无效的数字格式" except ZeroDivisionError: return "错误:数字不能为0" except Exception as e: return f"错误:{e}" # 方式2:一个 except 块捕获多个异常 def process_data2(data): try: result = int(data) result = 100 / result return result except (ValueError, ZeroDivisionError) as e: return f"错误:{type(e).__name__} - {e}" # 方式3:使用 Exception 捕获所有异常 def process_data3(data): try: result = int(data) result = 100 / result return result except Exception as e: return f"错误:{e}" # 测试 test_data = ["10", "0", "abc", None] print("方式1:") for data in test_data: print(f" {data} -> {process_data1(data)}") print("\n方式2:") for data in test_data: print(f" {data} -> {process_data2(data)}")multiple_exceptions()
2. 异常链和重新抛出
def exception_chaining(): """异常链和重新抛出""" def process_file(filename): """处理文件,可能抛出多种异常""" try: with open(filename, 'r') as f: data = f.read() return int(data) * 2 except FileNotFoundError as e: # 重新抛出更具体的异常 raise RuntimeError(f"配置文件 {filename} 不存在") from e except ValueError as e: # 重新抛出,保留原始异常 raise RuntimeError(f"配置文件内容不是有效数字") from e def safe_process(filename): """安全处理文件""" try: return process_file(filename) except RuntimeError as e: print(f"处理失败: {e}") print(f"原始错误: {e.__cause__}") return None # 测试 result = safe_process("nonexistent.txt") print(f"结果: {result}")exception_chaining()
3. try-except-else-finally 完整示例
def complete_example(): """完整的异常处理示例""" def divide_numbers(a, b): """安全的除法函数""" print(f"\n计算 {a} / {b}") try: # 尝试执行除法 result = a / b except ZeroDivisionError as e: # 处理除零错误 print(f" 除零错误: {e}") return None except TypeError as e: # 处理类型错误 print(f" 类型错误: {e}") return None else: # 没有异常时执行 print(f" 计算成功") return result finally: # 总是执行 print(f" 除法操作完成") # 测试各种情况 print("=== 正常情况 ===") result = divide_numbers(10, 2) print(f"结果: {result}") print("\n=== 除零情况 ===") result = divide_numbers(10, 0) print(f"结果: {result}") print("\n=== 类型错误 ===") result = divide_numbers("10", 2) print(f"结果: {result}")complete_example()
四、自定义异常
1. 创建自定义异常
def custom_exceptions(): """自定义异常类""" # 定义自定义异常 class ValidationError(Exception): """验证错误基类""" pass class EmptyFieldError(ValidationError): """空字段错误""" def __init__(self, field_name): self.field_name = field_name super().__init__(f"字段 '{field_name}' 不能为空") class InvalidEmailError(ValidationError): """无效邮箱错误""" pass class AgeRangeError(ValidationError): """年龄范围错误""" def __init__(self, age, min_age, max_age): self.age = age self.min_age = min_age self.max_age = max_age super().__init__(f"年龄 {age} 必须在 {min_age}-{max_age} 之间") class WeakPasswordError(ValidationError): """弱密码错误""" def __init__(self, reason): self.reason = reason super().__init__(f"密码强度不足: {reason}") # 使用自定义异常 def validate_user(name, email, age, password): """验证用户信息""" if not name: raise EmptyFieldError("name") if not email: raise EmptyFieldError("email") if '@' not in email or '.' not in email: raise InvalidEmailError(f"邮箱格式错误: {email}") if not (0 <= age <= 150): raise AgeRangeError(age, 0, 150) if len(password) < 8: raise WeakPasswordError("密码长度至少8位") if not any(c.isdigit() for c in password): raise WeakPasswordError("密码必须包含数字") if not any(c.isupper() for c in password): raise WeakPasswordError("密码必须包含大写字母") return {"name": name, "email": email, "age": age} # 测试用户验证 test_users = [ ("", "test@example.com", 25, "Password123"), ("Alice", "", 25, "Password123"), ("Bob", "invalid-email", 25, "Password123"), ("Charlie", "charlie@example.com", 200, "Password123"), ("David", "david@example.com", 25, "weak"), ("Eve", "eve@example.com", 25, "Password123"), ] for name, email, age, password in test_users: try: result = validate_user(name, email, age, password) print(f"✓ 验证通过: {result}") except EmptyFieldError as e: print(f"✗ 空字段错误: {e}") except InvalidEmailError as e: print(f"✗ 邮箱错误: {e}") except AgeRangeError as e: print(f"✗ 年龄错误: {e}") except WeakPasswordError as e: print(f"✗ 密码错误: {e}") except ValidationError as e: print(f"✗ 验证错误: {e}")custom_exceptions()
2. 异常处理装饰器
def exception_decorators(): """异常处理装饰器""" from functools import wraps def handle_exceptions(default_return=None, log_errors=True): """异常处理装饰器""" def decorator(func): @wraps(func) def wrapper(*args, **kwargs): try: return func(*args, **kwargs) except Exception as e: if log_errors: print(f"函数 {func.__name__} 发生错误: {type(e).__name__}: {e}") # 如果 default_return 是可调用对象,调用它 if callable(default_return): return default_return(e, *args, **kwargs) return default_return return wrapper return decorator def retry(max_attempts=3, delay=1, exceptions=(Exception,)): """重试装饰器""" def decorator(func): @wraps(func) def wrapper(*args, **kwargs): last_exception = None for attempt in range(max_attempts): try: return func(*args, **kwargs) except exceptions as e: last_exception = e print(f"尝试 {attempt + 1}/{max_attempts} 失败: {e}") if attempt < max_attempts - 1: import time time.sleep(delay) raise last_exception return wrapper return decorator # 使用装饰器 @handle_exceptions(default_return=0) def safe_divide(a, b): return a / b @handle_exceptions(default_return=lambda e, *args: f"错误: {e}") def get_user_data(user_id): if user_id <= 0: raise ValueError("用户ID无效") return {"id": user_id, "name": "User"} @retry(max_attempts=3, delay=0.5, exceptions=(ConnectionError, TimeoutError)) def unstable_network_call(): import random if random.random() < 0.7: raise ConnectionError("网络连接失败") return "成功" # 测试 print(safe_divide(10, 2)) # 5.0 print(safe_divide(10, 0)) # 0 print(get_user_data(1)) # {'id': 1, 'name': 'User'} print(get_user_data(-1)) # 错误: 用户ID无效 try: result = unstable_network_call() print(f"网络调用: {result}") except Exception as e: print(f"最终失败: {e}")exception_decorators()
五、上下文管理器中的异常处理
1. 自定义上下文管理器
def context_manager_exceptions(): """上下文管理器中的异常处理""" class Resource: """模拟资源""" def __init__(self, name): self.name = name self.opened = False def open(self): print(f"打开资源: {self.name}") self.opened = True def close(self): if self.opened: print(f"关闭资源: {self.name}") self.opened = False def use(self): if not self.opened: raise RuntimeError("资源未打开") print(f"使用资源: {self.name}") if self.name == "bad": raise ValueError("资源使用失败") class ResourceContext: """资源上下文管理器""" def __init__(self, resource): self.resource = resource def __enter__(self): self.resource.open() return self.resource def __exit__(self, exc_type, exc_val, exc_tb): self.resource.close() if exc_type is not None: print(f"上下文退出时捕获异常: {exc_type.__name__}: {exc_val}") # 可以选择是否抑制异常 # return True # 抑制异常 return False # 不抑制异常 # 使用 print("=== 正常使用 ===") try: with ResourceContext(Resource("good")) as res: res.use() except Exception as e: print(f"捕获异常: {e}") print("\n=== 发生异常 ===") try: with ResourceContext(Resource("bad")) as res: res.use() except Exception as e: print(f"捕获异常: {e}")context_manager_exceptions()
2. 使用 contextlib 简化
from contextlib import contextmanagerdef contextlib_examples(): """使用 contextlib 简化上下文管理器""" @contextmanager def managed_resource(name): """资源管理上下文""" print(f"获取资源: {name}") try: yield name except Exception as e: print(f"使用资源时发生异常: {e}") raise finally: print(f"释放资源: {name}") @contextmanager def ignored(*exceptions): """忽略指定异常的上下文""" try: yield except exceptions as e: print(f"忽略异常: {type(e).__name__}: {e}") @contextmanager def retry_context(max_retries=3, delay=1): """重试上下文""" attempt = 0 while attempt < max_retries: try: yield break except Exception as e: attempt += 1 print(f"尝试 {attempt}/{max_retries} 失败: {e}") if attempt < max_retries: import time time.sleep(delay) else: raise # 使用示例 print("=== 基本资源管理 ===") with managed_resource("database"): print("使用数据库") # raise ValueError("操作失败") print("\n=== 忽略异常 ===") with ignored(ZeroDivisionError, ValueError): result = 10 / 0 print(f"结果: {result}") print("\n=== 重试机制 ===") import random def unstable_operation(): if random.random() < 0.7: raise ConnectionError("网络错误") print("操作成功") with retry_context(max_retries=3, delay=0.5): unstable_operation()contextlib_examples()
六、异常处理的最佳实践
1. 异常处理原则
def best_practices(): """异常处理最佳实践""" # 1. 捕获具体异常,不要捕获所有异常 def bad_practice(): try: result = int(input("输入数字: ")) except Exception: # 太宽泛 print("输入错误") def good_practice(): try: result = int(input("输入数字: ")) except ValueError: # 具体异常 print("请输入有效的数字") except KeyboardInterrupt: # 处理中断 print("\n用户取消") # 2. 不要吞掉异常 def bad_silence(): try: result = 10 / 0 except: pass # 什么都不做,隐藏了错误 def good_logging(): import logging try: result = 10 / 0 except ZeroDivisionError as e: logging.error(f"除零错误: {e}") raise # 重新抛出 # 3. 使用 finally 清理资源 def resource_cleanup(): f = None try: f = open("file.txt", "r") data = f.read() except FileNotFoundError: print("文件不存在") finally: if f: f.close() # 确保文件被关闭 # 4. 使用 else 子句 def use_else(): try: result = 10 / 2 except ZeroDivisionError: print("除零错误") else: # 只有在没有异常时才执行 print(f"计算成功: {result}") # 5. 异常应该有意义 class BusinessError(Exception): """业务逻辑错误""" pass def validate_age(age): if age < 0: raise ValueError("年龄不能为负数") # 使用合适的异常类型 if age > 150: raise BusinessError("年龄超出合理范围") # 自定义异常 print("最佳实践演示完成")best_practices()
2. 性能考虑
def performance_considerations(): """异常处理的性能考虑""" import time # 异常处理比条件判断慢,但更清晰 def check_before_use(data, index): """LBYL: Look Before You Leap""" if 0 <= index < len(data): return data[index] return None def try_first(data, index): """EAFP: Easier to Ask for Forgiveness than Permission""" try: return data[index] except IndexError: return None # 性能测试 data = list(range(1000)) iterations = 100000 # 测试正常情况(索引有效) start = time.perf_counter() for i in range(iterations): result = check_before_use(data, 500) lbyl_time = time.perf_counter() - start start = time.perf_counter() for i in range(iterations): result = try_first(data, 500) eafp_time = time.perf_counter() - start print(f"LBYL (条件检查): {lbyl_time:.3f}秒") print(f"EAFP (异常处理): {eafp_time:.3f}秒") # 测试异常情况(索引无效) start = time.perf_counter() for i in range(iterations): result = check_before_use(data, 2000) lbyl_error_time = time.perf_counter() - start start = time.perf_counter() for i in range(iterations): result = try_first(data, 2000) eafp_error_time = time.perf_counter() - start print(f"\n异常情况:") print(f"LBYL: {lbyl_error_time:.3f}秒") print(f"EAFP: {eafp_error_time:.3f}秒") print("\n建议: 正常情况使用 EAFP,频繁异常使用 LBYL")performance_considerations()
七、总结
异常处理要点表格
| | |
|---|
try | | |
except | | |
else | | |
finally | | |
raise | | |
异常处理最佳实践总结
捕获具体异常:不要使用裸露的 except:
不要吞掉异常:记录或重新抛出
使用 finally 清理:确保资源释放
合理使用 else:区分正常和异常流程
创建自定义异常:提高代码可读性
考虑性能:在合适场景使用异常处理
文档说明:记录函数可能抛出的异常
异常处理是编写健壮 Python 程序的关键技能。通过合理使用 try-except 结构,可以优雅地处理各种错误情况,提高程序的稳定性和用户体验