欢迎来到 Python 学习计划的第 58 天!🎉
昨天我们学习了 try-except 基本语法,知道了如何防止程序崩溃。但仅仅捕获异常是不够的,我们需要知道发生了什么错误以及在哪里发生的。
今天我们将深入 异常对象内部,学习如何捕获特定异常、获取错误详情以及查看堆栈信息。这是编写健壮程序的关键一步!
一、捕获特定异常
Python 有丰富的内置异常类型,应该根据实际情况捕获特定的异常,而不是笼统地捕获所有错误。
1. 为什么要捕获特定异常?
- 精确处理:不同类型的错误需要不同的处理逻辑。
- 避免掩盖错误:防止意外捕获了未预期的错误(如
KeyboardInterrupt)。
2. 常见异常类型
# ValueError: 值错误try: value = int("abc")except ValueError: print("值转换错误")# ZeroDivisionError: 除零错误try: result = 10 / 0except ZeroDivisionError: print("除以零错误")# KeyError: 字典键不存在try: data = {"name": "张三"} print(data["age"])except KeyError: print("键不存在")
3. 捕获多种异常
可以使用元组一次性捕获,或使用多个 except 块。
# 方式 1:多个 except 块(推荐,便于区分处理)try: num = int(input("输入:")) result = 100 / numexcept ValueError: print("输入不是整数")except ZeroDivisionError: print("除数为零")# 方式 2:元组捕获(处理逻辑相同时)try: num = int(input("输入:")) result = 100 / numexcept (ValueError, ZeroDivisionError) as e: print(f"输入错误:{e}")
二、异常对象信息
使用 as 关键字可以将异常实例赋值给变量,从而访问错误的详细信息。
1. 基本属性
try: raise ValueError("这是错误信息")except ValueError as e: print(f"异常类型:{type(e).__name__}") # ValueError print(f"异常信息:{e}") # 这是错误信息 print(f"异常参数:{e.args}") # ('这是错误信息',)
2. 特定异常的特有属性
不同异常类型可能包含额外的信息(如文件名、错误号等)。
# FileNotFoundErrortry: with open("不存在的文件.txt") as f: passexcept FileNotFoundError as e: print(f"文件名:{e.filename}") # 不存在的文件.txt print(f"错误号:{e.errno}") # 2 print(f"错误信息:{e.strerror}") # No such file or directory# KeyErrortry: data = {"name": "张三"} value = data["age"]except KeyError as e: print(f"缺失的键:{e.args[0]}") # age
三、获取完整的堆栈信息
当需要记录详细错误日志时,可以使用 traceback 模块获取完整的错误堆栈。
1. 使用 traceback 模块
import tracebackdef inner_function(): raise ValueError("内部错误")def outer_function(): inner_function()try: outer_function()except ValueError as e: # 获取格式化的堆栈字符串 tb_str = traceback.format_exc() print("完整堆栈信息:") print(tb_str)
完整堆栈信息:Traceback (most recent call last): File "...", line 10, in <module> outer_function() File "...", line 7, in outer_function inner_function() File "...", line 4, in inner_function raise ValueError("内部错误")ValueError: 内部错误
2. 使用 sys.exc_info()
获取当前异常的详细信息(类型、值、堆栈跟踪)。
import systry: 1 / 0except ZeroDivisionError: exc_type, exc_value, exc_tb = sys.exc_info() print(f"异常类型:{exc_type}") print(f"异常值:{exc_value}") print(f"堆栈跟踪:{exc_tb}")
四、异常层次结构
Python 的异常形成层次结构,可以通过捕获父类异常来处理多种子类异常。
BaseException├── SystemExit├── KeyboardInterrupt└── Exception ├── ArithmeticError(算术错误) │ └── ZeroDivisionError ├── LookupError (查找错误) │ ├── IndexError │ └── KeyError ├── OSError (系统错误) │ └── FileNotFoundError ├── ValueError └── TypeError
捕获父类异常
try: # 可能抛出 IndexError 或 KeyError passexcept LookupError as e: # 捕获两者的父类 print(f"查找错误:{e}")
五、实战应用示例
1. 详细错误日志
在生产环境中,记录完整的错误信息便于排查问题。
import tracebackimport logginglogging.basicConfig(level=logging.ERROR)def divide(a, b): try: return a / b except ZeroDivisionError as e: logging.error(f"除法错误:{e}") logging.error(f"参数:a={a}, b={b}") logging.error(f"堆栈:\n{traceback.format_exc()}") return Noneresult = divide(10, 0)
2. 文件操作的详细错误信息
根据具体异常类型返回不同的错误提示。
def read_file_safe(filepath): try: with open(filepath, "r", encoding="utf-8") as f: return {"success": True, "content": f.read()} except FileNotFoundError as e: return { "success": False, "error": "文件不存在", "filename": e.filename } except PermissionError as e: return { "success": False, "error": "权限不足", "filename": e.filename } except UnicodeDecodeError as e: return { "success": False, "error": "编码错误", "encoding": e.encoding }result = read_file_safe("不存在.txt")print(result)
3. 自定义异常处理器
统一处理不同类型的异常,返回友好的错误消息。
def handle_exception(e): """统一的异常处理函数""" error_type = type(e).__name__ handlers = { "ValueError": lambda e: f"值错误:{e}", "TypeError": lambda e: f"类型错误:{e}", "KeyError": lambda e: f"键 '{e.args[0]}' 不存在", "FileNotFoundError": lambda e: f"文件 '{e.filename}' 不存在", } handler = handlers.get(error_type, lambda e: f"未知错误:{e}") return handler(e)# 测试errors = [ValueError("无效值"), KeyError("username")]for e in errors: print(handle_exception(e))
六、注意事项与最佳实践
建议 | 说明 |
|---|
具体优先 | 先捕获具体异常,再捕获通用异常(Exception) |
记录堆栈 | 生产环境应记录完整的堆栈信息(traceback) |
不要忽略 | 获取异常信息后要适当处理,不要直接 pass |
敏感信息 | 注意不要将详细错误信息直接暴露给最终用户 |
层次结构 | 利用异常继承关系,用父类捕获多种子类异常 |
常见误区
- 捕获所有异常:
except: 会捕获包括 KeyboardInterrupt 在内的所有错误,导致程序无法正常退出。 - 不记录日志:只打印简单错误信息,丢失了堆栈上下文,难以调试。
- 暴露敏感信息:将数据库错误或文件路径直接显示给用户,可能存在安全风险。
七、总结
知识点 | 说明 |
|---|
特定异常 | 捕获 ValueError, ZeroDivisionError 等具体类型 |
异常对象 | 使用 as e 获取,访问 args, filename 等属性 |
堆栈信息 | 使用 traceback.format_exc() 获取完整错误链 |
层次结构 | 可以通过捕获父类(如 LookupError)处理多种子类 |
最佳实践 | 具体优先,记录日志,不暴露敏感信息 |
掌握异常对象的详细信息,能让你的错误处理逻辑更加精准,调试问题更加高效。
📌 明日预告:else 与 finally 的执行时机
明天我们将进入 异常与文件模块第三天!
- 主题:else 与 finally 的执行时机
- 核心问题:
try-except-else-finally 的完整结构是什么?else 块什么时候执行?(没有异常时)finally 块什么时候执行?(无论是否异常)- 资源清理的最佳实践是什么?
- 执行顺序是怎样的?
💡 提前思考:
- 如果
try 块中没有异常,except 不执行,那 else 会执行吗? - 如果
try 块中有 return,finally 还会执行吗? - 为什么推荐用
finally 来关闭文件?
掌握特定异常捕获,让错误处理更精准!继续加油!🚀