Python的异常处理是编写健壮代码的核心技能。本文从实际案例出发,系统讲解try-except、异常层级、主动抛出异常、自定义异常及异常传递等知识点,并给出最佳实践和常见陷阱。
一、从文件读取错误看异常处理
假设我们要读取一个文件:- file_path = "data.txt"
- with open(file_path, 'r') as f:
- content = f.read()
- print("文件内容:", content)
复制代码
如果data.txt不存在,程序会直接崩溃并抛出FileNotFoundError。异常(Exception)是运行时发生的错误事件,会中断正常流程。没有异常处理,程序遇到错误就终止,用户体验极差。
二、Python内置异常的层级关系
所有异常都继承自BaseException。主要层级如下:
- BaseException
- SystemExit
- KeyboardInterrupt
- GeneratorExit
- Exception(绝大多数业务异常在此)
- StopIteration
- ArithmeticError (FloatingPointError, OverflowError, ZeroDivisionError)
- AssertionError
- AttributeError
- EOFError
- ImportError (ModuleNotFoundError)
- LookupError (IndexError, KeyError)
- NameError (UnboundLocalError)
- OSError (FileNotFoundError, PermissionError, TimeoutError)
- RuntimeError (NotImplementedError)
- SyntaxError (IndentationError)
- TypeError
- ValueError (UnicodeError)
- 以及其他Warning等
理解层级关系有助于精确捕获。例如捕获OSError可以同时处理FileNotFoundError和PermissionError。
三、try...except的三种捕获方式
基础语法:- try:
- result = 10 / 0
- except ZeroDivisionError:
- print("除数不能为零!")
- except (TypeError, ValueError) as e:
- print(f"类型或值错误:{e}")
- except Exception as e:
- print(f"发生未知错误:{e}")
复制代码
建议优先捕获具体异常,避免笼统捕获Exception。
四、获取异常详细信息的四种方法
通过traceback模块可以获取完整栈信息:- import traceback
- try:
- x = 1 / 0
- except ZeroDivisionError as e:
- print(f"异常对象:{e}") # 1
- print(f"异常类型:{type(e).__name__}") # 2
- print(f"完整信息:{traceback.format_exc()}") # 3
- print(f"异常参数:{e.args}") # 4
复制代码
五、try-except-else-finally完整流程
- def read_file_safely(filename):
- content = None
- try:
- print(f"尝试打开文件:{filename}")
- with open(filename, 'r') as f:
- content = f.read()
- except FileNotFoundError:
- print(f"文件 {filename} 不存在")
- except PermissionError:
- print(f"没有权限读取文件 {filename}")
- except OSError as e:
- print(f"系统错误:{e}")
- else:
- print("文件读取成功!")
- return content
- finally:
- print("文件操作结束,清理完成")
- return content # 注意:finally中的return会覆盖else中的return
- result = read_file_safely("data.txt")
复制代码
执行顺序:try → 命中except → else(无异常时) → finally。注意finally块中的return会覆盖前面的返回值,这是常见陷阱。
六、使用raise主动抛出异常
- def validate_age(age):
- if not isinstance(age, int):
- raise TypeError("年龄必须是整数")
- if age < 0:
- raise ValueError("年龄不能为负数")
- if age > 150:
- raise ValueError("年龄不能超过150岁")
- return True
- try:
- validate_age(-5)
- except ValueError as e:
- print(f"验证失败:{e}")
复制代码
raise可以附带自定义消息,也可在except块中重新抛出异常。
七、自定义异常类
继承Exception,添加业务属性:- class InsufficientFundsError(Exception):
- def __init__(self, balance, amount):
- self.balance = balance
- self.amount = amount
- message = f"余额不足!当前余额:{balance},需要:{amount}"
- super().__init__(message)
- class BankAccount:
- def __init__(self, balance):
- self.balance = balance
- def withdraw(self, amount):
- if amount > self.balance:
- raise InsufficientFundsError(self.balance, amount)
- self.balance -= amount
- return self.balance
- account = BankAccount(1000)
- try:
- account.withdraw(1500)
- except InsufficientFundsError as e:
- print(f"取款失败:{e}")
- print(f"余额={e.balance}, 请求={e.amount}")
复制代码
自定义异常使错误语义更清晰,便于调用方进行精确处理。
八、异常的传递机制
异常未捕获时会沿调用栈向上传递:- def function_a():
- print("函数A开始")
- function_b()
- print("函数A结束") # 不会执行
- def function_b():
- print("函数B开始")
- function_c()
- print("函数B结束") # 不会执行
- def function_c():
- print("函数C开始")
- raise ValueError("函数C中发生错误")
- print("函数C结束") # 不会执行
- try:
- function_a()
- except ValueError as e:
- print(f"在最外层捕获到异常:{e}")
- print("传递路径:function_c → function_b → function_a → 主程序")
复制代码
异常传递特点:从发生点向上传播,每层都可选择捕获或继续抛出;若最终无捕获,程序崩溃并显示完整调用栈。
九、最佳实践与常见陷阱
最佳实践:
- 优先捕获具体异常类
- 避免空except:(会捕获KeyboardInterrupt等)
- 在生产环境记录异常详情
- 使用finally或with确保资源释放
- 为业务逻辑创建自定义异常
常见陷阱:
1. 过于宽泛的捕获:except: 会吞掉SystemExit等
2. 吞掉异常信息:直接pass而不记录
3. finally中return覆盖try/else中的return值
十、总结
异常处理不是消灭所有错误,而是优雅地处理预期内的异常。掌握try-except-else-finally、异常层级、raise、自定义异常及传递机制,能显著提升代码的健壮性和可维护性。 |