异常处理:try/except/finally、常见异常类型
一句话概述
Python 异常处理通过 try/except/finally 结构来捕获并优雅地处理运行时错误(异常),防止程序崩溃。try 块放可能出错的代码,except 块捕获特定异常类型并处理,finally 块无论如何都会执行,适合做清理工作。
💡 核心要点:①异常是运行时错误,不处理会导致程序崩溃退出 ②try包裹可能出错的代码,except按异常类型分别捕获处理 ③finally中的代码无论是否异常都会执行,适合释放资源 ④常见异常包括FileNotFoundError、ValueError、TypeError、ZeroDivisionError等
教学与演示
一、什么是异常——程序也会"生病"
是什么:异常(Exception)是程序运行时发生的错误。当 Python 解释器遇到无法执行的代码时,会"抛出"(raise)一个异常对象。如果这个异常没有被捕获处理,就会沿着调用栈向上传播,最终导致程序崩溃并打印错误信息(Traceback)。
大白话 异常就像是你在路上踩到的"坑"——踩中了如果不处理,就摔倒了(程序崩溃)。但如果你提前知道前面可能有坑,做好了准备(try/except),踩到坑时就能稳住(捕获异常),然后绕过去继续走。程序不会崩溃,继续优雅运行。
为什么:现实世界的程序不可能永远不出错——用户可能输入了非法数据,文件可能不存在,网络可能断开,内存可能不够。如果一遇到错误就崩溃,用户体验极差。异常处理让程序有了"容错能力",能够预期可能发生的错误并做出妥善应对。
怎么做:
import numpy as np
# ===== 模拟各种常见异常及其影响 =====
def simulate_operation(op_name, success_rate):
"""模拟一个可能失败的操作"""
roll = np.random.random()
if roll < success_rate:
return f"✅ {op_name} 成功", True
else:
return f"❌ {op_name} 失败", False
np.random.seed(42)
operations = [
("读取配置文件", 0.9),
("连接数据库", 0.8),
("加载模型权重", 0.7),
("预处理数据集", 0.95),
("开始训练", 0.85),
]
print("【模拟程序执行流程】")
print("如果不处理异常,任何一步失败都会导致程序崩溃。\n")
success_count = 0
fail_count = 0
for op_name, rate in operations:
result, ok = simulate_operation(op_name, rate)
print(f" {result}")
if ok:
success_count += 1
else:
fail_count += 1
print(f" ⚠️ 如果没有 try/except,程序在这里就崩溃了!")
print(f"\n统计:成功 {success_count}/{len(operations)},失败 {fail_count}/{len(operations)}")
print(f"成功率:{success_count/len(operations)*100:.0f}%")
print(f"说明:在真实程序中,我们需要 try/except 让失败的操作不拖垮整个程序。")什么用:在 AI 训练流程中,异常处理是"防崩溃"的关键——数据加载出错时可以跳过坏样本继续训练,模型保存失败时可以重试,网络超时时可以切换备用数据源。没有异常处理的生产级 AI 系统是不可想象的。
二、try/except——程序的"安全气囊"
是什么:try/except 是 Python 最基本的异常处理结构。try 块中放可能出错的代码;如果出错,Python 跳到 except 块执行,不会崩溃。except 可以指定捕获的异常类型,也可以有多个 except 分支分别处理不同类型的异常。
大白话 就像开车时的安全气囊——正常行驶时不启动(try块正常执行),一旦发生碰撞(出现异常),安全气囊立即弹出保护你(except块接管),而不是让车毁人亡(程序崩溃)。
为什么:不同异常需要不同的处理方式——文件不存在提示用户检查路径,网络错误可以自动重试,数据类型错误可以尝试转换。except 的分类型捕获让程序能"对症下药",而不是一刀切地报个错就完事。
怎么做:
import numpy as np
# ===== try/except 基本结构演示 =====
# 模拟数据预处理中的异常处理
def safe_divide(a, b):
"""安全除法:处理除零异常"""
try:
result = a / b
return result, "正常"
except ZeroDivisionError:
return np.nan, "除零错误→返回 NaN"
def safe_sqrt(x):
"""安全开方:处理负数异常"""
try:
result = np.sqrt(x)
return result, "正常"
except (ValueError, RuntimeWarning):
return np.nan, "负数开方→返回 NaN"
# 测试各种边界情况
test_cases = [
(10, 2, "正常除法"),
(10, 0, "除零"),
(16, 4, "正常开方"),
(-9, None, "负数开方"),
]
print("【try/except 安全处理演示】\n")
for case in test_cases:
a, b, desc = case
if b is not None:
val, status = safe_divide(a, b)
print(f"{desc}:{a}/{b} = {val}({status})")
else:
val, status = safe_sqrt(a)
print(f"{desc}:sqrt({a}) = {val}({status})")
# ===== 数值统计 =====
print(f"\n【统计】")
vals = [safe_divide(a, b)[0] for a, b in [(10,2), (10,0), (20,5), (30,0)]]
vals_arr = np.array(vals)
print(f"结果数组:{vals_arr}")
print(f"有效值数量:{np.sum(~np.isnan(vals_arr))} / {len(vals_arr)}")
print(f"NaN 数量:{np.sum(np.isnan(vals_arr))}(被异常处理的除零操作)")什么用:在 AI 数据处理中,类似的安全包装必不可少——safe_divide 防止除零导致训练中断,safe_sqrt 防止负数输入导致 NaN 传播。数据清洗时对每条记录用 try/except 包裹,坏数据跳过、好数据继续。
三、finally——无论如何都要执行的"清理工"
是什么:finally 块中的代码无论是否发生异常都会执行。即使 try 中有 return、break、continue,finally 也会执行。它通常用于释放资源(关闭文件、断开网络连接、释放锁等)。
大白话 就像你出门前一定会锁门——不管你今天过得好不好,有没有遇到意外,回家后门都是锁好的。finally就是那个"无论如何都会锁门"的动作。就算try里发生了异常(遇到了意外),finally也保证资源被正确释放。
为什么:有些资源必须释放,否则会导致泄漏——文件不关闭会耗尽文件描述符,数据库连接不还回连接池会导致连接耗尽,锁不释放会导致死锁。finally 提供了最可靠的清理保障,即使异常发生也不会跳过清理代码。
怎么做:
import numpy as np
# ===== finally 块:无论如何都执行的清理代码 =====
def simulate_resource_usage(scenarios):
"""
模拟资源使用场景(如打开文件、数据库连接等)
演示 finally 如何保证资源总是被释放
"""
results = []
for i, (scenario, will_fail) in enumerate(scenarios):
resource_opened = False
try:
# 步骤1:获取资源(模拟 open()、connect() 等)
resource_opened = True
print(f"\n场景{i+1}:「{scenario}」")
print(f" 🔓 资源已打开")
# 步骤2:使用资源
if will_fail:
# 模拟使用过程中出现异常
raise RuntimeError(f"{scenario}时发生错误")
# 正常处理
data = np.random.randn(100)
print(f" 📊 处理数据:均值={np.mean(data):.3f}, 标准差={np.std(data):.3f}")
results.append(("成功", np.mean(data)))
except RuntimeError as e:
print(f" ❌ 异常:{e}")
results.append(("失败", np.nan))
finally:
# 无论成功还是失败,都释放资源
if resource_opened:
print(f" 🔒 资源已关闭(finally 保证)")
return results
# 模拟 4 个场景:有些成功,有些失败
scenarios = [
("读取训练数据", False), # 成功
("预处理文本", True), # 失败
("加载图片", False), # 成功
("保存模型", True), # 失败
]
print("【finally 清理保证演示】")
print("注意观察:即使 '预处理文本' 和 '保存模型' 抛出异常,资源仍然被正确关闭!\n")
results = simulate_resource_usage(scenarios)
# 汇总
successes = sum(1 for r in results if r[0] == "成功")
print(f"\n【汇总】")
print(f"总操作:{len(results)},成功:{successes},失败:{len(results) - successes}")
print(f"✅ 所有场景下资源都被 finally 正确释放!")什么用:在 AI 训练中,GPU 显存是昂贵资源——训练结束后必须释放。用 try/finally 确保即使训练中途因 OOM(显存不足)崩溃,也能释放已分配的显存。数据加载器中的文件句柄、数据库连接池的连接,都依赖 finally 做清理。
四、常见异常类型一本通
是什么:Python 内置了丰富的异常类型,每种对应一类错误场景。了解常见异常类型,能让你写出更精准的 except 捕获代码。
| 异常类型 | 触发场景 | 示例 |
|---|---|---|
FileNotFoundError | 打开不存在的文件 | open('no.txt') |
TypeError | 类型不匹配的操作 | '5' + 3 |
ValueError | 类型对但值不对 | int('abc') |
ZeroDivisionError | 除以零 | 5 / 0 |
IndexError | 索引超出列表范围 | [1,2][5] |
KeyError | 字典键不存在 | {}['k'] |
AttributeError | 对象没有该属性 | 'str'.not_exist() |
ImportError | 导入模块失败 | import notamodule |
大白话 就像是不同的"疾病名称"——发烧(TypeError)、骨折(ValueError)、感冒(KeyError)。知道病名才能对症下药,知道异常类型才能精准捕获处理。
为什么:except 如果不指定类型(裸 except:),会捕获所有异常,包括 KeyboardInterrupt(Ctrl+C)和 SystemExit,导致程序无法正常退出。精准指定异常类型是良好编程习惯,也方便排查问题。
怎么做:
import numpy as np
# ===== 常见异常类型演示与安全处理 =====
def demo_exception_types():
"""演示各类常见异常及其安全处理方式"""
test_cases = []
# 1. FileNotFoundError 模拟
try:
# open('不存在的文件.txt') # 真实代码会抛出 FileNotFoundError
raise FileNotFoundError("不存在的文件.txt")
except FileNotFoundError as e:
test_cases.append(("FileNotFoundError", str(e), "检查文件路径,提示用户"))
# 2. ValueError 模拟
try:
int("abc123") # 无法转换
except ValueError as e:
test_cases.append(("ValueError", str(e), "用 try/except 包裹,给默认值"))
# 3. TypeError 模拟
try:
result = "hello" + 42
except TypeError as e:
test_cases.append(("TypeError", str(e), "先转换类型 str(42) 再操作"))
# 4. ZeroDivisionError 模拟
try:
x = 100 / 0
except ZeroDivisionError as e:
test_cases.append(("ZeroDivisionError", str(e), "除前检查分母,或返回 inf/NaN"))
# 5. IndexError 模拟
try:
arr = [1, 2, 3]
val = arr[100]
except IndexError as e:
test_cases.append(("IndexError", str(e), "访问前检查 len(),或用 .get()"))
return test_cases
print("【常见异常类型速查表】\n")
print(f"{'异常类型':<22} {'错误信息':<40} {'处理建议'}")
print("-" * 90)
results = demo_exception_types()
for exc_type, msg, suggestion in results:
print(f"{exc_type:<22} {msg:<40} {suggestion}")
# ===== 异常类型的重要性:用 numpy 模拟 =====
print(f"\n【为什么要区分异常类型?】")
scenarios = np.array([
"文件不存在 → FileNotFoundError → 提示用户检查路径",
"除数为零 → ZeroDivisionError → 跳过该条计算",
"数据格式错 → ValueError → 尝试修复或丢弃",
"网络超时 → TimeoutError → 自动重试",
])
for s in scenarios:
print(f" • {s}")
print(f"\n不同异常需要不同处理策略,所以 except 要指定类型!")什么用:在 AI 数据处理流水线中,每种异常的处理策略都不同——FileNotFoundError 可能是路径配置错了需要人工介入,ValueError 可能只是某条脏数据可以跳过,MemoryError 可能需要减小 batch size 重新训练。区分异常类型让容错策略更加精细化。
概念关系图谱
| 概念 | 核心含义 | 与AI的关系 | 关联概念 |
|---|---|---|---|
| 异常 | 运行时错误,打断正常流程 | 训练中断、数据加载失败 | try/except、错误类型 |
try | 包裹可能出错的代码块 | 包裹数据加载、模型推理等关键步骤 | except、finally |
except | 捕获并处理指定类型的异常 | 分类处理不同数据质量问题 | try、异常类型 |
finally | 无论如何都执行的清理块 | 释放 GPU 显存、关闭数据流 | try、资源管理 |
FileNotFoundError | 文件/路径不存在 | 数据集路径配置错误 | 文件操作、open() |
ValueError | 值不合法 | 数据格式错误、参数越界 | 数据类型、转换 |
重点答疑
Q1:except Exception: 和裸 except: 有什么区别?
except Exception: 只捕获普通异常(程序逻辑错误),不捕获 KeyboardInterrupt(Ctrl+C)、SystemExit(sys.exit())等系统级退出信号。裸 except: 会捕获一切,包括这些退出信号,导致程序可能无法正常终止。永远不要用裸 except:,至少用 except Exception:。
Q2:一个 try 可以有多个 except 吗?执行顺序是怎样的?
可以。多个 except 按顺序从上到下匹配,匹配到第一个符合条件的就执行,后续的 except 不再检查。因此要把具体的异常类型放前面,宽泛的(如 Exception)放最后。如果 except ZeroDivisionError 放在 except Exception 后面,那除零错误会被 Exception 先捕获,更具体的处理逻辑永远不会执行。
Q3:finally 和 except 可以同时使用吗?执行顺序是什么?
可以。执行顺序:try 正常 → else(如果有)→ finally;try 异常 → 匹配的 except → finally。finally 永远是最后一个执行的。即使 except 中有 return,finally 也会在 return 之前执行。
章节单词汇总
| 英文 | 音标 | 术语/释义 |
|---|---|---|
| exception | /ɪkˈsepʃn/ | 异常;程序运行时的错误 |
| try | /traɪ/ | 尝试;包裹可能出错的代码 |
| except | /ɪkˈsept/ | 除外;捕获并处理异常 |
| finally | /ˈfaɪnəli/ | 最终;无论如何都会执行的代码块 |
| raise | /reɪz/ | 抛出;主动触发异常 |
| traceback | /ˈtreɪsbæk/ | 回溯;异常发生时的调用栈信息 |
| crash | /kræʃ/ | 崩溃;程序非正常终止 |
| gracefully | /ˈɡreɪsfəli/ | 优雅地;程序不崩溃而是妥善处理错误 |
面试练习
Q1 [单选] 以下哪个不是 Python 内置异常类型?
- A.
ValueError - B.
TypeError - C.
NullPointerError - D.
KeyError
解答:Python 没有NullPointerError(那是 Java 的概念),Python 中类似情况会抛出AttributeError或TypeError(对None调用方法)。ABD 都是 Python 内置异常。
Q2 [单选] try/except/finally 中,finally 块什么时候不执行?
- A.
try块正常执行完毕时 - B.
try块抛出异常时 - C.
except块中有return语句时 - D. 程序被
os._exit()强制终止时
解答:finally几乎总是执行,唯一的例外是程序被强制终止(如os._exit(0)直接退出进程或系统断电)。即使except中有return,finally也会在return之前执行。
Q3 [单选] int('abc') 会抛出什么异常?
- A.
TypeError - B.
ValueError - C.
SyntaxError - D.
NameError
解答:参数类型是正确的(str),但字符串的值'abc'无法被解释为整数,因此抛出ValueError(值错误)。
Q4 [单选] 以下哪种写法是正确的多异常处理?
- A.
except (ValueError, TypeError) as e: - B.
except ValueError, TypeError as e: - C.
except ValueError and TypeError as e: - D.
except ValueError + TypeError as e:
解答:捕获多种异常类型时,用元组 (Type1, Type2) 包裹。B 是 Python 2 的语法,Python 3 已废弃。
Q5 [单选] 在 except 中获取异常对象用什么关键字?
- A.
catch - B.
as - C.
from - D.
with
解答:except ValueError as e:中的e就是异常对象,可以访问e.args、str(e)等属性。注意不是catch(那是 Java/JavaScript 的语法)。
Q6 [多选] 以下哪些场景适合使用 finally?
- A. 关闭已打开的文件
- B. 释放数据库连接
- C. 在异常发生后打印日志
- D. 释放线程锁
解答:finally适合资源清理(关闭文件、释放连接、释放锁)。C(打印日志)应该放在except块中,因为只有异常发生时才需要记录。
Q7 [单选] 以下代码的输出是什么?
try:
print("A")
raise ValueError()
print("B")
except ValueError:
print("C")
finally:
print("D")- A. A B C D
- B. A C D
- C. A C
- D. A D
解答:执行顺序:try打印 A → 抛出异常跳转到except→ B 被跳过 →except打印 C →finally打印 D。输出 A C D。
Q8 [单选] except Exception as e: 和 except BaseException as e: 的区别是什么?
- A. 完全一样
- B.
BaseException范围更大,包含KeyboardInterrupt等系统级异常 - C.
Exception范围更大 - D.
BaseException只能捕获文件异常
解答:Python 异常层次:BaseException是所有异常的基类,其子类Exception是普通异常的基类,另一个子类KeyboardInterrupt等是系统级异常。except Exception不捕获系统级异常,更安全。
Q9 [单选] 当 try 和 except 中都有 return 时,finally 中的 return 会怎样?
- A.
finally的return不生效 - B.
finally的return会覆盖try/except的返回值 - C. 三个
return都执行 - D. 直接报错
解答:finally中的return会覆盖try或except中准备返回的值。因此不要在finally中写return,这会导致异常被"吞掉"且返回值被意外覆盖。
Q10 [多选] 关于 Python 异常处理,以下哪些说法是正确的?
- A. 异常被
except捕获后,程序继续执行后续代码 - B. 如果
except中没有匹配的类型,异常会继续向上传播 - C.
try块中越少的代码越好,只放可能出错的代码 - D.
except可以省略异常类型,捕获所有异常
解答:ABC。D 虽然语法正确,但不推荐(裸except:会捕获系统退出信号)。A 正确——异常被处理后程序继续;B 正确——未匹配的异常会沿调用栈传播;C 正确——try块应尽量精简,精准包裹可能出错的代码。