Try和expect的正确使用方式
💥
什么是异常(Exception)
程序运行时发生的错误事件,若不处理会导致程序崩溃
⚠️异常 vs 错误
Python 中有两类问题:语法错误(SyntaxError)在代码运行前就被发现;异常(Exception)在运行时才会触发。try/except 只能处理运行时异常,语法错误必须手动修复代码。
常见异常类型一览
ValueError
值类型正确但内容不合法
int("abc")
TypeError
操作或函数作用于错误类型
"5" + 5
IndexError
序列索引超出范围
[1,2,3][10]
KeyError
字典中键不存在
{}["key"]
ZeroDivisionError
除数为零
10 / 0
FileNotFoundError
文件或目录不存在
open("no.txt")
AttributeError
对象没有该属性或方法
None.split()
ImportError
导入模块失败
import xyz
📝
基本语法结构
try/except 完整语法格式与各子句功能
📌 完整语法格式
try:放置可能引发异常的代码块,程序会先尝试执行这里
except:当 try 块中发生异常时执行,可指定捕获特定异常类型
else:没有发生异常时才执行(可选),用于放置正常逻辑
finally:无论是否发生异常都会执行(可选),常用于资源清理
basic_syntax.py
# ===== 最简单的 try/except ===== try: result = 10 / 0 # 这里会触发 ZeroDivisionError except ZeroDivisionError: print("❌ 错误:除数不能为零!") # 输出:❌ 错误:除数不能为零! # ===== 获取异常信息 ===== try: num = int("hello") # ValueError except ValueError as e: # as e 获取异常对象 print(f"❌ 异常类型:{type(e).__name__}") print(f"❌ 异常信息:{e}") # 输出: # 异常类型:ValueError # 异常信息:invalid literal for int() with base 10: 'hello'🔄
执行流程图解
通过流程图理解 try / except / else / finally 的执行顺序
try → 若有异常走 except → 若无异常走 else → 最终无论如何都执行 finally
1
🟢 try 块开始执行
Python 逐行执行 try 块中的代码,一旦某行抛出异常,立即跳转到对应的 except 块,try 块剩余代码不再执行
2
🔴 异常匹配 except
Python 从上到下依次检查 except 子句,找到第一个匹配的异常类型就执行,若无匹配则异常继续向上层传播
3
🔵 else(无异常时才执行)
只有 try 块中没有发生任何异常时才执行 else,用于放置依赖 try 成功执行的逻辑
4
🟣 finally(必定执行)
无论是否发生异常,finally 块始终执行,常用于关闭文件、释放数据库连接、清理资源
flow_demo.py — 演示完整执行顺序
def divide(a, b): print("[try] 开始执行...") try: result = a / b print(f"[try] 计算结果 = {result}") except ZeroDivisionError as e: print(f"[except] 捕获异常:{e}") else: print("[else] 没有发生异常,执行 else") finally: print("[finally] 无论如何都会执行\n") # 测试1:正常情况(b != 0) divide(10, 2) # [try] 开始执行... # [try] 计算结果 = 5.0 # [else] 没有发生异常,执行 else # [finally] 无论如何都会执行 # 测试2:异常情况(b = 0) divide(10, 0) # [try] 开始执行... # [except] 捕获异常:division by zero # [finally] 无论如何都会执行🎯
多个 except 子句
针对不同异常类型分别处理,精准捕获
💡原则:精确优先,父类靠后
子类异常要写在父类之前,否则会被父类先匹配到。例如 FileNotFoundError 是 OSError 的子类,应写在 OSError 前面。
multi_except.py
# ===== 捕获多个不同类型的异常 ===== def safe_input_calc(user_input, divisor): try: num = int(user_input) # 可能 ValueError lst = [1, 2, 3] item = lst[num] # 可能 IndexError result = item / divisor # 可能 ZeroDivisionError return result except ValueError: print("⚠️ 请输入有效的整数") except IndexError: print("⚠️ 索引超出列表范围") except ZeroDivisionError: print("⚠️ 除数不能为零") except Exception as e: # 兜底,捕获其他所有异常 print(f"❌ 未知错误:{e}") safe_input_calc("abc", 1) # ⚠️ 请输入有效的整数 safe_input_calc("9", 1) # ⚠️ 索引超出列表范围 safe_input_calc("1", 0) # ⚠️ 除数不能为零 # ===== 一个 except 捕获多种异常(元组形式)===== try: x = int("hello") except (ValueError, TypeError) as e: print(f"值或类型错误:{e}")| 写法 | 说明 | 示例 |
|---|---|---|
except ExceptionType: | 捕获指定类型异常 | except ValueError: |
except ExceptionType as e: | 捕获并获取异常对象 | except ValueError as e: |
except (T1, T2) as e: | 同时捕获多种类型 | except (ValueError, TypeError): |
except Exception as e: | 捕获所有标准异常(兜底) | ⚠️ 不推荐滥用 |
except: | 捕获所有内容(含 SystemExit) | ❌ 强烈不推荐 |
✅
else 与 finally 子句
else 在无异常时执行;finally 永远执行,用于资源清理
file_handling.py — finally 资源清理经典案例
# ===== 文件操作中 finally 的关键作用 ===== def read_file(filepath): f = None try: f = open(filepath, 'r', encoding='utf-8') content = f.read() print(f"✅ 读取成功,共 {len(content)} 个字符") return content except FileNotFoundError: print(f"❌ 文件不存在:{filepath}") return None except PermissionError: print("❌ 没有权限读取此文件") return None else: print("📌 else:文件读取完全正常") # 无异常时执行 finally: if f: # 无论如何都关闭文件 f.close() print("🔒 finally:文件已安全关闭") # 更推荐写法:with 语句(自动关闭文件) def read_file_better(filepath): try: with open(filepath, 'r', encoding='utf-8') as f: return f.read() # with 自动处理 close() except FileNotFoundError: print("❌ 文件不存在") return None🟡
else 的使用场景
- 将"成功后的逻辑"与 try 分离,代码更清晰
- 避免 try 块过长、意外捕获 else 中的异常
- 只在真正没有异常时才执行数据库写入等操作
🟣
finally 的使用场景
- 关闭文件句柄、网络连接、数据库游标
- 释放锁(threading.Lock)
- 打印/记录日志(确保一定执行)
- UI 中隐藏 Loading 加载动画
🌳
Python 异常层级体系
了解异常继承关系,才能正确使用 except 捕获
Python 异常继承自 BaseException → Exception,捕获父类会同时捕获所有子类异常
| 异常类 | 触发条件 | 常见示例 |
|---|---|---|
BaseException | 所有异常的根类 | 不建议直接捕获 |
Exception | 所有标准异常的基类 | 兜底时可捕获 |
ValueError | 值不合法 | int("abc") |
TypeError | 类型不匹配 | "a" + 1 |
IndexError | 列表索引越界 | lst[100] |
KeyError | 字典键不存在 | d["x"] |
AttributeError | 属性/方法不存在 | None.upper() |
FileNotFoundError | 文件不存在(OSError 子类) | open("x.txt") |
ZeroDivisionError | 除数为零(ArithmeticError子类) | 1/0 |
ImportError | 导入失败 | import xyz |
StopIteration | 迭代器耗尽 | next(iter([])) |
KeyboardInterrupt | 用户按 Ctrl+C | BaseException 直接子类 |
🏗
自定义异常类
继承 Exception,为业务逻辑创建语义明确的专属异常
自定义异常继承自 Exception,可按业务层级建立异常树,如 AppError → NetworkError / DatabaseError
custom_exception.py
# ===== 定义自定义异常 ===== class AppError(Exception): """应用层基础异常""" def __init__(self, message, code=None): super().__init__(message) self.code = code def __str__(self): return f"[错误码 {self.code}] {super().__str__()}" class NetworkError(AppError): """网络请求相关异常""" pass class DatabaseError(AppError): """数据库操作相关异常""" def __init__(self, message, table=None): super().__init__(message, code=500) self.table = table # ===== 使用自定义异常 ===== def connect_db(host): if not host: raise DatabaseError("数据库主机地址不能为空", table="users") print(f"✅ 连接到 {host}") try: connect_db("") except DatabaseError as e: print(f"数据库错误:{e}") print(f"问题表:{e.table}") except AppError as e: # 父类兜底 print(f"应用错误:{e}") # 输出: # 数据库错误:[错误码 500] 数据库主机地址不能为空 # 问题表:users⭐
最佳实践与注意事项
写出健壮、可维护的异常处理代码
左侧(Bad):捕获过宽、吞掉错误;右侧(Good):精确捕获、记录日志、合理处理
❌反面案例 — 不要这样写
不要用裸except:捕获所有异常(会连 KeyboardInterrupt 也捕获);不要捕获异常后什么都不做(异常静默是 Bug 的温床);不要用异常控制正常程序流。
best_practice.py
import logging logging.basicConfig(level=logging.ERROR, format='%(asctime)s - %(message)s') # ❌ 反面示例 1:捕获所有异常后静默 try: result = risky_operation() except: # 危险!裸 except 连 Ctrl+C 都拦截 pass # 危险!异常被吞掉,出问题根本不知道 # ✅ 正确示例 1:精确捕获 + 日志记录 try: result = risky_operation() except ValueError as e: logging.error(f"数值错误:{e}") # 记录日志 result = None # 给出安全默认值 # ✅ 正确示例 2:捕获后重新抛出(re-raise) def process_data(data): try: return int(data) * 2 except ValueError as e: logging.error(f"数据格式异常,原始数据:{data}") raise # 重新抛出,让上层决定如何处理 # ✅ 正确示例 3:异常链(保留原始异常信息) def load_config(path): try: with open(path) as f: return f.read() except FileNotFoundError as e: raise RuntimeError(f"配置文件加载失败:{path}") from e # from e 保留原始异常,调试更友好✅
应该这样做
- 捕获最精确的异常类型
- 使用 logging 记录异常信息
- 为用户提供有意义的错误提示
- 使用
raise重新抛出不能处理的异常 - 用
with语句代替 try/finally 管理资源
❌
避免这样做
- 裸
except:捕获一切 - 捕获后只写
pass(吞掉异常) - 在 except 里做复杂逻辑导致新异常
- 捕获 BaseException(除非顶层入口)
- 用异常来做正常流程控制
🧩
综合实战案例
模拟真实场景:用户输入处理 + 文件读写 + 网络请求
案例一:安全的用户输入处理
case1_input.py
def get_valid_age(): """循环获取合法年龄,直到用户输入正确""" while True: try: age_str = input("请输入您的年龄:") age = int(age_str) # 可能 ValueError if age < 0 or age > 150: raise ValueError("年龄必须在 0-150 之间") except ValueError as e: print(f"⚠️ 输入无效:{e},请重新输入") else: print(f"✅ 年龄 {age} 已记录") return age # 成功则返回 age = get_valid_age() print(f"您的年龄是:{age}")案例二:带重试机制的网络请求
case2_network.py
import urllib.request import time def fetch_url(url, max_retries=3, timeout=5): """带重试机制的 HTTP 请求""" for attempt in range(1, max_retries + 1): try: print(f"🔄 第 {attempt} 次尝试请求 {url}") with urllib.request.urlopen(url, timeout=timeout) as resp: data = resp.read().decode('utf-8') print(f"✅ 请求成功,响应长度:{len(data)} 字符") return data except urllib.error.URLError as e: print(f"❌ 网络错误(第 {attempt} 次):{e.reason}") if attempt < max_retries: print(f" 等待 {attempt} 秒后重试...") time.sleep(attempt) # 指数退避 print("❌ 全部重试失败,返回 None") return None result = fetch_url("https://example.com")案例三:JSON 配置文件读取
case3_config.py
import json import os def load_config(config_path: str) -> dict: """安全读取 JSON 配置文件""" try: with open(config_path, 'r', encoding='utf-8') as f: config = json.load(f) # 校验必须的字段 required = ['host', 'port', 'database'] missing = [k for k in required if k not in config] if missing: raise KeyError(f"配置缺少必要字段:{missing}") return config except FileNotFoundError: print(f"❌ 配置文件不存在:{config_path}") return {} except json.JSONDecodeError as e: print(f"❌ JSON 格式错误:第 {e.lineno} 行 - {e.msg}") return {} except KeyError as e: print(f"❌ 配置校验失败:{e}") return {} except Exception as e: print(f"❌ 未知错误:{type(e).__name__}: {e}") return {} config = load_config("config.json") print(config)📝
练习题
动手写代码,检验学习成果
🏋️ 动手练习
练习 1 — 基础编写一个函数safe_divide(a, b),捕获除零异常和类型异常,正常时返回结果,异常时返回 None 并打印错误原因。
练习 2 — 进阶编写read_numbers(filename),读取文件中每行的数字,遇到非数字行跳过(捕获 ValueError),文件不存在时返回空列表,最后无论如何都打印"读取完成"。
练习 3 — 自定义异常创建AgeError自定义异常类,编写validate_age(age):当 age 不是整数抛出 TypeError,当 age 不在 0-150 之间抛出 AgeError,并附带错误码。
练习 4 — 综合模拟简单计算器:循环接收用户输入的算式(如 "10 / 2"),解析并计算,使用完整的 try/except/else/finally 处理各种可能的异常,输入 "quit" 时退出循环。
💡快速记忆口诀
try放危险代码,except捕捉异常,else成功才走,finally总收尾。捕获要精确,日志要记录,不能吞异常,需要就重抛!
