异常处理:try/except/finally、常见异常类型

一句话概述

Python 异常处理通过 try/except/finally 结构来捕获并优雅地处理运行时错误(异常),防止程序崩溃。try 块放可能出错的代码,except 块捕获特定异常类型并处理,finally 块无论如何都会执行,适合做清理工作。

💡 核心要点:①异常是运行时错误,不处理会导致程序崩溃退出 ②try 包裹可能出错的代码,except 按异常类型分别捕获处理 ③finally 中的代码无论是否异常都会执行,适合释放资源 ④常见异常包括 FileNotFoundErrorValueErrorTypeErrorZeroDivisionError

教学与演示

一、什么是异常——程序也会"生病"

是什么:异常(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 中有 returnbreakcontinuefinally 也会执行。它通常用于释放资源(关闭文件、断开网络连接、释放锁等)。

大白话 就像你出门前一定会锁门——不管你今天过得好不好,有没有遇到意外,回家后门都是锁好的。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包裹可能出错的代码块包裹数据加载、模型推理等关键步骤exceptfinally
except捕获并处理指定类型的异常分类处理不同数据质量问题try、异常类型
finally无论如何都执行的清理块释放 GPU 显存、关闭数据流try、资源管理
FileNotFoundError文件/路径不存在数据集路径配置错误文件操作、open()
ValueError值不合法数据格式错误、参数越界数据类型、转换

重点答疑

Q1:except Exception: 和裸 except: 有什么区别?

except Exception: 只捕获普通异常(程序逻辑错误),不捕获 KeyboardInterrupt(Ctrl+C)、SystemExitsys.exit())等系统级退出信号。裸 except: 会捕获一切,包括这些退出信号,导致程序可能无法正常终止。永远不要用裸 except:,至少用 except Exception:

Q2:一个 try 可以有多个 except 吗?执行顺序是怎样的?

可以。多个 except 按顺序从上到下匹配,匹配到第一个符合条件的就执行,后续的 except 不再检查。因此要把具体的异常类型放前面,宽泛的(如 Exception)放最后。如果 except ZeroDivisionError 放在 except Exception 后面,那除零错误会被 Exception 先捕获,更具体的处理逻辑永远不会执行。

Q3:finallyexcept 可以同时使用吗?执行顺序是什么?

可以。执行顺序:try 正常 → else(如果有)→ finallytry 异常 → 匹配的 exceptfinallyfinally 永远是最后一个执行的。即使 except 中有 returnfinally 也会在 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 中类似情况会抛出 AttributeErrorTypeError(对 None 调用方法)。ABD 都是 Python 内置异常。

Q2 [单选] try/except/finally 中,finally 块什么时候不执行?

  • A. try 块正常执行完毕时
  • B. try 块抛出异常时
  • C. except 块中有 return 语句时
  • D. 程序被 os._exit() 强制终止时
解答:finally 几乎总是执行,唯一的例外是程序被强制终止(如 os._exit(0) 直接退出进程或系统断电)。即使 except 中有 returnfinally 也会在 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.argsstr(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 [单选] 当 tryexcept 中都有 return 时,finally 中的 return 会怎样?

  • A. finallyreturn 不生效
  • B. finallyreturn 会覆盖 try/except 的返回值
  • C. 三个 return 都执行
  • D. 直接报错
解答:finally 中的 return 会覆盖 tryexcept 中准备返回的值。因此不要在 finally 中写 return,这会导致异常被"吞掉"且返回值被意外覆盖。

Q10 [多选] 关于 Python 异常处理,以下哪些说法是正确的?

  • A. 异常被 except 捕获后,程序继续执行后续代码
  • B. 如果 except 中没有匹配的类型,异常会继续向上传播
  • C. try 块中越少的代码越好,只放可能出错的代码
  • D. except 可以省略异常类型,捕获所有异常
解答:ABC。D 虽然语法正确,但不推荐(裸 except: 会捕获系统退出信号)。A 正确——异常被处理后程序继续;B 正确——未匹配的异常会沿调用栈传播;C 正确——try 块应尽量精简,精准包裹可能出错的代码。