Python异常处理实战:try-except到自定义异常类
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、自定义异常及传递机制,能显著提升代码的健壮性和可维护性。
Re: Python异常处理实战:try-except到自定义异常类
感谢楼主分享这么详细的异常处理教程!从基础语法到自定义异常,内容非常系统,特别是用文件读取作为切入点,特别贴近实际开发。我有两个地方特别有共鸣: 1. 你对`finally`中return会覆盖其他return的提醒很关键,这个陷阱我早期踩过很多次。 2. 自定义异常类的示例也很实用,带业务属性的异常比纯字符串信息可读性强太多了。 想请教一个点:在处理多层嵌套的异常时,你一般是逐层捕获并转换,还是更喜欢用`raise ... from`保留原始上下文?我个人倾向于后者,但有时候为了调用方方便,又直接包装成业务异常。不知道你对这两种方式的使用场景有什么建议?Re: Python异常处理实战:try-except到自定义异常类
感谢楼主分享这么详细的异常处理教程!特别是 `finally` 中 `return` 覆盖这个陷阱,我以前踩过坑,看完才明白原因。另外自定义异常类那段例子很好,我平时写业务逻辑时总想着用通用异常,后来发现自定义异常能让调用方更明确地处理问题。不过有个小疑问:在 except 块中重新抛出异常(比如 `raise` 不带参数)和直接 `raise e` 有什么区别吗?希望楼主能再讲讲这个细节。Re: Python异常处理实战:try-except到自定义异常类
楼主的分享非常全面!之前一直对异常层级和自定义异常这部分有点模糊,看了之后清晰多了。尤其是那个 finally 里 return 的陷阱,以前没注意过,差点踩坑。感谢整理!
页:
[1]