7、python异常处理
Python 内置异常适用于通用错误,但业务系统中需要更具体的错误描述(如「用户余额不足」「订单状态异常」)。通过自定义异常,可实现错误的分类管理和精准反馈。自定义异常实现规则继承自 Python 内置的Exception类(不要继承,它包含系统级异常如 KeyboardInterrupt);通过__init__方法自定义异常信息,可添加业务相关的额外属性(如错误码);用raise语句主动抛出自定
Python 异常处理与特性
本章学习知识点
- 异常捕获:try-except-else-finally、自定义异常
- 调试技巧:print 调试、断点调试、日志模块(logging)
在 Python 开发中,代码运行时的错误(异常)和逻辑漏洞难以完全避免。异常处理能让程序在遇到错误时优雅降级而非直接崩溃,调试技巧则能快速定位并修复漏洞。
一、异常处理
异常是程序运行时发生的意外情况(如除零错误、文件不存在、类型不匹配等)。Python 提供了 try-except 系列语句捕获异常,并通过自定义异常实现业务场景的错误封装。
1.1、异常处理结构
try-except-else-finally: 这是 Python 异常处理的标准范式,四个关键字各司其职,形成完整的错误处理闭环。其执行逻辑为:尝试执行核心代码 → 捕获指定异常 → 无异常则执行额外逻辑 → 无论是否异常都执行收尾操作。
-
基础结构解析
try: # 1. 尝试执行的核心业务代码(可能发生异常的代码) risky_operation() except 异常类型1 as e: # 2. 捕获到「异常类型1」时执行的处理逻辑 handle_error1(e) # e 为异常对象,包含错误信息 except (异常类型2, 异常类型3) as e: # 3. 捕获多个异常(用元组包裹),统一处理 handle_errors2_3(e) except Exception as e: # 4. 捕获所有其他未明确指定的异常(兜底,不建议滥用) handle_all_other_errors(e) else: # 5. 仅当 try 块无异常时才执行(可选) # 用于执行正常流程的后续操作 normal_operation() finally: # 6. 无论是否发生异常,必定执行(可选) # 用于资源释放等收尾操作 release_resource() -
关键组件作用与实战场景
-
try 块:精准包裹风险代码
-
作用:仅包裹「可能触发异常的核心代码」(如文件读写、网络请求、数据类型转换、数值计算等),无关代码不纳入,避免掩盖异常真实触发位置。
-
示例: 仅将「文件打开 + 读取」放入 try,而非整个业务逻辑
# 推荐写法 def read_file(file_path): try: # 仅包裹可能抛异常的代码 with open(file_path, "r", encoding="utf-8") as f: return f.read() except FileNotFoundError: print(f"文件 {file_path} 不存在")
-
-
except 块:精准捕获指定异常
-
核心原则:避免用
except Exception:兜底捕获,需按「异常类型精准匹配」,Python 常见核心异常如下:异常类型 含义 典型触发场景 TypeError 数据类型不匹配 1 + “2”(整数与字符串拼接) ValueError 数据值无效 int (“abc”)(非数字字符串转整数) ZeroDivisionError 除零错误 10 / 0(除数为 0) FileNotFoundError 文件 / 路径不存在 open(“nonexistent.txt”) KeyError 字典键不存在 {“a”:1}[“b”] IndexError 序列索引越界 [1,2,3][10](列表下标超出范围) -
示例:按类型分别捕获,而非一刀切
try: num = int(input("请输入数字:")) res = 10 / num except ValueError: print("输入的不是有效数字") except ZeroDivisionError: print("不能输入0作为除数")
-
-
else 块:分离正常逻辑与异常处理
-
作用:仅在 try 块无异常时执行「正常流程的后续操作」(如数据校验通过后写入数据库、计算结果格式化等),让正常逻辑与异常处理解耦,提升可读性。
-
示例
try: num = int(input("请输入数字:")) except ValueError: print("输入非数字") else: # 无异常时执行正常逻辑 print(f"输入的数字是:{num},平方值为:{num**2}")
-
-
finally 块:强制释放资源
-
核心作用:执行「必须完成的资源释放操作」(如关闭文件句柄、断开数据库连接、释放锁、终止网络请求等)。
-
关键特性:无论 try/except/else 块中是否有
return、break等语句,finally 块都会优先执行,确保资源不泄漏。 -
示例
conn = None try: conn = get_db_connection() # 建立数据库连接 conn.execute("INSERT INTO t_user VALUES (1, 'test')") except DatabaseError: print("数据库操作失败") finally: # 无论是否异常,均关闭连接 if conn: conn.close()
-
-
-
实战:文件读写的完整异常处理
-
文件读写涉及「文件不存在、权限不足、读写错误」等多种异常,用完整范式处理可确保程序稳健运行,并正确释放文件资源。
def read_file(file_path): # 初始化文件句柄为 None file = None try: # 尝试打开文件并读取内容(核心操作) file = open(file_path, "r", encoding="utf-8") content = file.read() except FileNotFoundError as e: # 捕获「文件不存在」异常 print(f"错误:文件 {file_path} 不存在 → {e}") return None except PermissionError as e: # 捕获「权限不足」异常 print(f"错误:无权限读取文件 → {e}") return None except UnicodeDecodeError as e: # 捕获「编码解析」异常 print(f"错误:文件编码不是 UTF-8 → {e}") return None else: # 无异常时执行:返回读取内容(正常流程) print(f"文件 {file_path} 读取成功,长度:{len(content)} 字节") return content finally: # 无论是否异常,都关闭文件(资源释放) if file: # 确保文件句柄已创建 file.close() print("文件句柄已关闭") # 测试:读取存在的文件 read_file("test.txt") # 测试:读取不存在的文件 read_file("nonexistent.txt")优化技巧:Python 提供
with语句(上下文管理器),可自动管理文件、网络连接等资源,替代 finally 块的手动释放。上述代码可简化为with open(...) as file: content = file.read(),无需手动关闭文件。
-
1.2、自定义异常
Python 内置异常适用于通用错误,但业务系统中需要更具体的错误描述(如「用户余额不足」「订单状态异常」)。通过自定义异常,可实现错误的分类管理和精准反馈。
-
自定义异常实现规则
- 继承自 Python 内置的
Exception类(不要继承BaseException,它包含系统级异常如 KeyboardInterrupt); - 通过
__init__方法自定义异常信息,可添加业务相关的额外属性(如错误码); - 用
raise语句主动抛出自定义异常。
- 继承自 Python 内置的
-
实战:电商订单的自定义异常
-
针对电商订单场景,定义「余额不足、订单不存在、状态异常」三种自定义异常,实现业务错误的精准捕获和处理。
# 1. 定义自定义异常(继承自 Exception) class OrderError(Exception): """订单相关异常的基类(所有订单错误都继承此类)""" def __init__(self, error_code, message): self.error_code = error_code # 业务错误码(便于前端处理) self.message = message # 错误描述 super().__init__(f"[{error_code}] {message}") # 调用父类构造 class InsufficientBalanceError(OrderError): """余额不足异常(继承自订单异常基类)""" def __init__(self, balance, need): # 错误码:1001,携带余额和所需金额信息 super().__init__(1001, f"余额不足,当前余额:{balance},所需金额:{need}") class OrderNotFoundError(OrderError): """订单不存在异常""" def __init__(self, order_id): super().__init__(1002, f"订单 {order_id} 不存在") class OrderStatusError(OrderError): """订单状态异常(如已取消的订单再次支付)""" def __init__(self, order_id, current_status): super().__init__(1003, f"订单 {order_id} 状态异常,当前状态:{current_status}") # 2. 业务逻辑函数(主动抛出自定义异常) def create_order(user_id, order_id, amount): """创建订单:检查余额,生成订单""" # 模拟用户余额查询 user_balance = 500 # 假设用户余额为 500 # 模拟订单状态查询 existing_orders = {"OD12345": "已支付"} # 已存在的订单 # 检查订单是否已存在 if order_id in existing_orders: raise OrderStatusError(order_id, existing_orders[order_id]) # 检查余额是否充足 if user_balance < amount: raise InsufficientBalanceError(user_balance, amount) # 无异常则创建订单 print(f"订单 {order_id} 创建成功,用户 {user_id} 支付金额:{amount}") # 3. 调用业务函数(捕获自定义异常) try: create_order(user_id=1001, order_id="OD12345", amount=600) except InsufficientBalanceError as e: print(f"创建订单失败(余额问题):{e.message},错误码:{e.error_code}") except OrderNotFoundError as e: print(f"创建订单失败(订单不存在):{e.message},错误码:{e.error_code}") except OrderStatusError as e: print(f"创建订单失败(状态问题):{e.message},错误码:{e.error_code}") except OrderError as e: # 捕获所有订单相关的其他异常(基类兜底) print(f"创建订单失败:{e.message},错误码:{e.error_code}")运行结果:
创建订单失败(状态问题):订单 OD12345 状态异常,当前状态:已支付,错误码:1003
-
二、调试技巧
调试是排查代码逻辑错误、异常原因的过程。Python 提供了「简单打印调试、专业断点调试、日志记录调试」三种核心方式,分别适用于不同场景(开发初期、复杂逻辑、生产环境)。
2.1、打印调试
通过 print 语句输出变量值、代码执行流程,适用于 小型脚本、简单逻辑 的快速调试。优点是成本低、无需额外工具;缺点是调试后需删除打印语句,易遗漏。
-
关键打印技巧
- 打印变量类型和值:用
print(f"变量名: {var}, 类型: {type(var)}")定位类型错误; - 标记执行流程:在分支、循环中打印
print("进入 if 分支"),确认代码执行路径; - 打印中间结果:在复杂计算中插入打印,如
print(f"步骤1结果: {res1}"),定位哪一步出现偏差。
- 打印变量类型和值:用
-
实战:打印调试定位排序逻辑错误
def bubble_sort(nums): """冒泡排序函数(存在逻辑错误)""" n = len(nums) for i in range(n): # 打印当前轮次和待排序数组 print(f"第 {i+1} 轮开始,数组:{nums}") for j in range(n - i): # 错误:应为 range(n - i - 1) # 打印当前比较的索引和值 print(f" 比较 j={j} ({nums[j]}) 和 j+1={j+1} ({nums[j+1]})") if nums[j] > nums[j+1]: # 交换后打印数组 nums[j], nums[j+1] = nums[j+1], nums[j] print(f" 交换后数组:{nums}") return nums # 测试排序 test_nums = [3, 1, 4, 1, 5] sorted_nums = bubble_sort(test_nums) print("最终排序结果:", sorted_nums)通过打印输出可发现:最后一轮仍在比较已排序完成的元素,且可能出现
index out of range错误,定位到内层循环条件应为range(n - i - 1)。
2.2、断点调试
断点调试是通过 IDE(如 PyCharm、VS Code)设置断点,让程序执行到断点处暂停,此时可逐行执行代码、查看变量实时值、观察调用栈,适用于 复杂逻辑、嵌套代码 的深度调试。
-
流程(以 VS Code 为例)
- 设置断点:点击代码行号左侧,出现红色圆点(断点),通常设置在「可能出错的代码行」或「逻辑分支入口」;
- 启动调试:点击 IDE 左侧「运行和调试」图标,选择「Python 文件」启动调试,程序会执行到第一个断点处暂停;
- 逐行执行: 「单步跳过(F10)」:执行当前行,不进入函数内部;
- 「单步调试(F11)」:执行当前行,若为函数则进入函数内部;
- 「继续(F5)」:从当前断点继续执行到下一个断点;
- 「跳出(Shift+F11)」:从当前函数内部跳出。
- 查看状态:调试面板可查看「变量」(实时值)、「调用栈」(函数调用层级)、「断点列表」(管理所有断点)。
-
实战:断点调试定位函数返回值错误
-
假设以下代码计算阶乘时返回值错误,用断点调试定位问题:
def factorial(n): """计算 n 的阶乘(存在逻辑错误)""" result = 1 if n < 0: return "输入不能为负数" while n > 0: result *= n n += 1 # 错误:应为 n -= 1 return result # 测试阶乘 print(factorial(5)) # 预期输出 120,实际进入死循环
-
更多推荐



所有评论(0)