查看: 232|回复: 3

Python异常处理实战:try-except到自定义异常类

[复制链接]
发表于 昨天 12:00 | 显示全部楼层 |阅读模式
Python的异常处理是编写健壮代码的核心技能。本文从实际案例出发,系统讲解try-except、异常层级、主动抛出异常、自定义异常及异常传递等知识点,并给出最佳实践和常见陷阱。

一、从文件读取错误看异常处理

假设我们要读取一个文件:
  1. file_path = "data.txt"
  2. with open(file_path, 'r') as f:
  3.     content = f.read()
  4.     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的三种捕获方式

基础语法:
  1. try:
  2.     result = 10 / 0
  3. except ZeroDivisionError:
  4.     print("除数不能为零!")
  5. except (TypeError, ValueError) as e:
  6.     print(f"类型或值错误:{e}")
  7. except Exception as e:
  8.     print(f"发生未知错误:{e}")
复制代码

建议优先捕获具体异常,避免笼统捕获Exception。

四、获取异常详细信息的四种方法

通过traceback模块可以获取完整栈信息:
  1. import traceback
  2. try:
  3.     x = 1 / 0
  4. except ZeroDivisionError as e:
  5.     print(f"异常对象:{e}")                     # 1
  6.     print(f"异常类型:{type(e).__name__}")      # 2
  7.     print(f"完整信息:{traceback.format_exc()}")  # 3
  8.     print(f"异常参数:{e.args}")                # 4
复制代码

五、try-except-else-finally完整流程
  1. def read_file_safely(filename):
  2.     content = None
  3.     try:
  4.         print(f"尝试打开文件:{filename}")
  5.         with open(filename, 'r') as f:
  6.             content = f.read()
  7.     except FileNotFoundError:
  8.         print(f"文件 {filename} 不存在")
  9.     except PermissionError:
  10.         print(f"没有权限读取文件 {filename}")
  11.     except OSError as e:
  12.         print(f"系统错误:{e}")
  13.     else:
  14.         print("文件读取成功!")
  15.         return content
  16.     finally:
  17.         print("文件操作结束,清理完成")
  18.         return content  # 注意:finally中的return会覆盖else中的return
  19. result = read_file_safely("data.txt")
复制代码

执行顺序:try → 命中except → else(无异常时) → finally。注意finally块中的return会覆盖前面的返回值,这是常见陷阱。

六、使用raise主动抛出异常
  1. def validate_age(age):
  2.     if not isinstance(age, int):
  3.         raise TypeError("年龄必须是整数")
  4.     if age < 0:
  5.         raise ValueError("年龄不能为负数")
  6.     if age > 150:
  7.         raise ValueError("年龄不能超过150岁")
  8.     return True
  9. try:
  10.     validate_age(-5)
  11. except ValueError as e:
  12.     print(f"验证失败:{e}")
复制代码

raise可以附带自定义消息,也可在except块中重新抛出异常。

七、自定义异常类

继承Exception,添加业务属性:
  1. class InsufficientFundsError(Exception):
  2.     def __init__(self, balance, amount):
  3.         self.balance = balance
  4.         self.amount = amount
  5.         message = f"余额不足!当前余额:{balance},需要:{amount}"
  6.         super().__init__(message)
  7. class BankAccount:
  8.     def __init__(self, balance):
  9.         self.balance = balance
  10.     def withdraw(self, amount):
  11.         if amount > self.balance:
  12.             raise InsufficientFundsError(self.balance, amount)
  13.         self.balance -= amount
  14.         return self.balance
  15. account = BankAccount(1000)
  16. try:
  17.     account.withdraw(1500)
  18. except InsufficientFundsError as e:
  19.     print(f"取款失败:{e}")
  20.     print(f"余额={e.balance}, 请求={e.amount}")
复制代码

自定义异常使错误语义更清晰,便于调用方进行精确处理。

八、异常的传递机制

异常未捕获时会沿调用栈向上传递:
  1. def function_a():
  2.     print("函数A开始")
  3.     function_b()
  4.     print("函数A结束")  # 不会执行
  5. def function_b():
  6.     print("函数B开始")
  7.     function_c()
  8.     print("函数B结束")  # 不会执行
  9. def function_c():
  10.     print("函数C开始")
  11.     raise ValueError("函数C中发生错误")
  12.     print("函数C结束")  # 不会执行
  13. try:
  14.     function_a()
  15. except ValueError as e:
  16.     print(f"在最外层捕获到异常:{e}")
  17.     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、自定义异常及传递机制,能显著提升代码的健壮性和可维护性。
回复

使用道具 举报

发表于 昨天 12:05 | 显示全部楼层

Re: Python异常处理实战:try-except到自定义异常类

感谢楼主分享这么详细的异常处理教程!从基础语法到自定义异常,内容非常系统,特别是用文件读取作为切入点,特别贴近实际开发。我有两个地方特别有共鸣: 1. 你对`finally`中return会覆盖其他return的提醒很关键,这个陷阱我早期踩过很多次。 2. 自定义异常类的示例也很实用,带业务属性的异常比纯字符串信息可读性强太多了。 想请教一个点:在处理多层嵌套的异常时,你一般是逐层捕获并转换,还是更喜欢用`raise ... from`保留原始上下文?我个人倾向于后者,但有时候为了调用方方便,又直接包装成业务异常。不知道你对这两种方式的使用场景有什么建议?
回复 支持 反对

使用道具 举报

发表于 昨天 12:05 | 显示全部楼层

Re: Python异常处理实战:try-except到自定义异常类

感谢楼主分享这么详细的异常处理教程!特别是 `finally` 中 `return` 覆盖这个陷阱,我以前踩过坑,看完才明白原因。另外自定义异常类那段例子很好,我平时写业务逻辑时总想着用通用异常,后来发现自定义异常能让调用方更明确地处理问题。不过有个小疑问:在 except 块中重新抛出异常(比如 `raise` 不带参数)和直接 `raise e` 有什么区别吗?希望楼主能再讲讲这个细节。
回复 支持 反对

使用道具 举报

发表于 昨天 12:05 | 显示全部楼层

Re: Python异常处理实战:try-except到自定义异常类

楼主的分享非常全面!之前一直对异常层级和自定义异常这部分有点模糊,看了之后清晰多了。尤其是那个 finally 里 return 的陷阱,以前没注意过,差点踩坑。感谢整理!
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-7-21 08:14 , Processed in 0.031182 second(s), 17 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部