欢迎光临
我们一直在努力

一文吃透 Python 异常处理:`try/except/else/finally` 的执行顺序、陷阱与实战

一文吃透 Python 异常处理:try/except/else/finally 的执行顺序、陷阱与实战

在 Python 编程中,异常处理几乎无处不在。

读取文件时,文件可能不存在;调用接口时,网络可能超时;处理用户输入时,字符串可能无法转换成数字;操作数据库时,事务可能执行失败。面对这些不确定性,我们通常会使用:

try:
...
except:
...
else:
...
finally:
...

很多初学者知道这些关键字“分别是做什么的”,却常常说不清它们到底按照什么顺序执行。

例如:

  • try 成功后,先执行 else 还是 finally?
  • except 中出现 return,finally 还会不会运行?
  • finally 中再次 return,会发生什么?
  • try 中抛出异常,但 finally 又抛出新异常,最终看到的是哪一个?
  • else 中出现异常,会被当前的 except 捕获吗?
  • break、continue 与 finally 一起使用时,执行顺序又是什么?

这些问题看似只是语法细节,实际上直接关系到文件关闭、数据库事务、锁释放、日志记录和服务稳定性。

这篇文章将从基础规则出发,结合大量代码示例和工程案例,系统讲清楚 Python 中 try/except/else/finally 的执行顺序、常见陷阱与最佳实践。


一、先看完整结构

Python 异常处理的完整形式如下:

try:
# 可能出现异常的代码
...
except SomeError:
# 处理指定异常
...
except AnotherError:
# 处理另一种异常
...
else:
# try 中没有发生异常时执行
...
finally:
# 无论是否发生异常,通常都会执行
...

需要注意的是,并不是每次都必须写完整结构。

常见组合包括:

try:
...
except SomeError:
...

try:
...
finally:
...

try:
...
except SomeError:
...
else:
...

try:
...
except SomeError:
...
finally:
...

但不能只写一个孤立的 try:

# 语法错误
try:
do_something()

try 后面至少要跟一个 except 或 finally。


二、四个代码块分别负责什么?

在理解执行顺序之前,先明确每个部分的职责。

1. try:放置可能发生异常的代码

try:
number = int(input("请输入一个整数:"))

try 不是“保护所有代码”的保险箱,而是一个异常监控区域。

一旦 try 中某一行抛出异常,当前 try 块剩余的代码将停止执行,程序开始寻找匹配的 except。

例如:

try:
print("步骤 1")
result = 10 / 0
print("步骤 2")
except ZeroDivisionError:
print("发生除零错误")

输出:

步骤 1
发生除零错误

“步骤 2”不会执行,因为异常发生后,程序已经跳出了 try 块。


2. except:处理匹配的异常

try:
result = 10 / 0
except ZeroDivisionError:
print("除数不能为 0")

当异常类型与某个 except 匹配时,对应代码块会执行。

还可以捕获异常对象:

try:
number = int("Python")
except ValueError as exc:
print(f"转换失败:{exc}")

输出类似:

转换失败:invalid literal for int() with base 10: 'Python'

多个异常可以分别处理:

try:
value = int(input("请输入数字:"))
result = 100 / value
except ValueError:
print("输入内容不是整数")
except ZeroDivisionError:
print("输入不能为 0")

也可以让多个异常共享处理逻辑:

try:
value = int(input("请输入数字:"))
result = 100 / value
except (ValueError, ZeroDivisionError) as exc:
print(f"输入不合法:{exc}")


3. else:仅在 try 没有异常时执行

try:
number = int("100")
except ValueError:
print("转换失败")
else:
print(f"转换成功:{number}")

输出:

转换成功:100

如果 try 中发生异常并进入 except,else 不会执行。

例如:

try:
number = int("Python")
except ValueError:
print("转换失败")
else:
print(f"转换成功:{number}")

输出:

转换失败

else 的语义可以理解为:

只有当 try 完整、正常执行结束时,才进入 else。


4. finally:离开异常结构前执行清理

try:
print("正在执行任务")
finally:
print("释放资源")

输出:

正在执行任务
释放资源

无论 try 是否发生异常,finally 通常都会执行。

因此它常用于:

  • 关闭文件;
  • 关闭网络连接;
  • 释放锁;
  • 回滚或清理事务;
  • 删除临时文件;
  • 记录任务结束状态。

三、最核心的执行顺序

可以先记住下面两条主线。

情况一:try 没有发生异常

执行顺序为:

try → else → finally

示例:

try:
print("1. try 开始")
result = 10 / 2
print("2. try 结束")
except ZeroDivisionError:
print("3. except")
else:
print("4. else")
finally:
print("5. finally")

输出:

1. try 开始
2. try 结束
4. else
5. finally

由于没有异常:

  • 完整执行 try;
  • 跳过所有 except;
  • 执行 else;
  • 最后执行 finally。

  • 情况二:try 发生异常,且被成功捕获

    执行顺序为:

    try → except → finally

    示例:

    try:
    print("1. try 开始")
    result = 10 / 0
    print("2. try 结束")
    except ZeroDivisionError:
    print("3. except")
    else:
    print("4. else")
    finally:
    print("5. finally")

    输出:

    1. try 开始
    3. except
    5. finally

    因为发生了 ZeroDivisionError:

  • try 在异常位置停止;
  • 匹配并执行 except ZeroDivisionError;
  • 跳过 else;
  • 执行 finally。

  • 情况三:try 发生异常,但没有匹配的 except

    执行顺序为:

    try → finally → 异常继续向外传播

    示例:

    try:
    print("try 开始")
    number = int("Python")
    except ZeroDivisionError:
    print("除零错误")
    finally:
    print("finally 执行")

    输出类似:

    try 开始
    finally 执行
    Traceback …
    ValueError: invalid literal for int() with base 10: 'Python'

    这里发生的是 ValueError,但当前只捕获 ZeroDivisionError,因此:

  • 当前 except 不匹配;
  • finally 仍然执行;
  • ValueError 继续向调用者传播。

  • 四、一张流程图看懂执行逻辑

    可以把整体流程理解为:

    ┌──────────────┐
    │ 进入 try 块 │
    └──────┬───────┘

    ┌──────────▼──────────┐
    │ try 中是否发生异常? │
    └──────┬────────┬─────┘
    │否 │是
    │ │
    ┌──────▼───┐ ┌──▼─────────────┐
    │执行 else │ │寻找匹配的 except│
    └──────┬───┘ └──┬─────────┬───┘
    │ │匹配 │不匹配
    │ ┌───▼────┐ │
    │ │执行 except│ │
    │ └───┬────┘ │
    └──────────┼──────────┘

    ┌──────▼──────┐
    │执行 finally │
    └──────┬──────┘

    ┌────────────▼────────────┐
    │正常结束或继续传播异常 │
    └─────────────────────────┘

    最简记忆口诀是:

    无异常:try → else → finally
    有异常:try → except → finally
    未捕获:try → finally → 向外抛


    五、为什么需要 else?全部写进 try 不行吗?

    很多人会写:

    try:
    number = int(user_input)
    result = calculate(number)
    save_result(result)
    except ValueError:
    print("输入格式错误")

    这段代码的问题是,try 范围过大。

    ValueError 不一定来自 int(user_input),也可能来自:

    calculate(number)

    或:

    save_result(result)

    这样程序可能把计算逻辑或数据保存中的错误,误判成“输入格式错误”。

    更好的写法是:

    try:
    number = int(user_input)
    except ValueError:
    print("输入格式错误")
    else:
    result = calculate(number)
    save_result(result)

    这样 except ValueError 只负责处理输入转换错误。

    如果 calculate() 或 save_result() 发生异常,不会被当前这个 except 错误捕获。

    因此,else 的重要价值是:

    缩小 try 块范围,让异常来源更明确。

    这是一条非常重要的 Python 最佳实践。


    六、else 中的异常会被前面的 except 捕获吗?

    不会。

    来看示例:

    try:
    number = int("100")
    except ValueError:
    print("转换失败")
    else:
    result = 10 / 0
    print(result)

    这里 try 中没有异常,所以进入 else。

    但 else 中发生了 ZeroDivisionError,它不会回头交给前面的 except ValueError 处理,而是直接向外传播。

    即使前面的 except 写的是 Exception,也不会捕获 else 中的异常:

    try:
    number = int("100")
    except Exception:
    print("发生异常")
    else:
    result = 10 / 0

    仍然会抛出 ZeroDivisionError。

    原因是前面的 except 只负责监控 try 块,不负责监控 else 块。

    要处理 else 中的异常,需要在外层继续使用异常处理:

    try:
    try:
    number = int("100")
    except ValueError:
    print("转换失败")
    else:
    result = 10 / 0
    except ZeroDivisionError:
    print("后续计算发生除零错误")

    不过,实际开发中不应为了“捕获一切”无限嵌套。更好的做法通常是合理划分函数职责。


    七、except 中出现异常会怎样?

    如果 except 在处理原异常时,又出现了新的异常,新异常会向外传播。

    try:
    result = 10 / 0
    except ZeroDivisionError:
    print(undefined_variable)

    原始异常是:

    ZeroDivisionError

    但在异常处理过程中,访问了不存在的变量,又产生:

    NameError

    最终 Python 会显示异常链,指出:

  • 最初发生了除零错误;
  • 在处理除零错误时,又发生了名称错误。
  • 即使 except 中再次发生异常,finally 仍然会执行:

    try:
    result = 10 / 0
    except ZeroDivisionError:
    print(undefined_variable)
    finally:
    print("清理资源")

    输出顺序大致是:

    清理资源
    Traceback …

    然后新的 NameError 向外传播。


    八、finally 到底有多“坚定”?

    finally 的特点是:程序离开 try 结构之前,通常会先执行它。

    即使代码中出现:

    • return
    • break
    • continue
    • 未捕获异常
    • 已捕获异常

    finally 依然通常会执行。


    九、try 中有 return,finally 还执行吗?

    会。

    def get_value():
    try:
    print("try")
    return 100
    finally:
    print("finally")

    result = get_value()
    print(result)

    输出:

    try
    finally
    100

    执行过程是:

  • try 准备返回 100;
  • Python 暂时保存这个返回值;
  • 执行 finally;
  • finally 正常结束;
  • 函数最终返回 100。
  • 可以把它理解为:

    准备 return → 先执行 finally → 再真正 return


    十、except 中有 return,finally 还执行吗?

    也会。

    def divide(a, b):
    try:
    return a / b
    except ZeroDivisionError:
    return None
    finally:
    print("计算结束")

    print(divide(10, 0))

    输出:

    计算结束
    None

    虽然 except 已经准备返回 None,但仍会先执行 finally。


    十一、最危险的陷阱:finally 中写 return

    来看下面的代码:

    def example():
    try:
    return "try 返回值"
    finally:
    return "finally 返回值"

    print(example())

    输出:

    finally 返回值

    为什么?

    因为 finally 中的 return 覆盖了 try 中原本准备返回的结果。

    这会让代码行为变得非常反直觉。

    更严重的是,finally 中的 return 甚至可能吞掉异常:

    def dangerous():
    try:
    result = 10 / 0
    finally:
    return "正常返回"

    print(dangerous())

    输出:

    正常返回

    ZeroDivisionError 消失了。

    这类代码极其危险,因为它可能让程序在发生严重错误时,表现得像正常完成一样。

    因此应牢记:

    不要在 finally 中写 return,除非你完全理解并明确需要覆盖原结果或异常。

    在绝大多数业务代码中,finally 应专注于清理,而不是控制返回值。

    不推荐:

    def load_data():
    try:
    return read_data()
    finally:
    return []

    推荐:

    def load_data():
    resource = open_resource()

    try:
    return read_data(resource)
    finally:
    resource.close()


    十二、finally 中抛出异常会发生什么?

    如果 try 中原本没有异常,但 finally 抛出异常,最终会传播 finally 的异常。

    try:
    print("正常执行")
    finally:
    raise RuntimeError("finally 出错")

    最终抛出:

    RuntimeError: finally 出错

    如果 try 中已经发生异常,finally 又抛出新的异常,那么新的异常通常会成为最终向外传播的异常。

    try:
    result = 10 / 0
    finally:
    raise RuntimeError("清理失败")

    这里先发生:

    ZeroDivisionError

    随后 finally 又发生:

    RuntimeError

    最终程序主要抛出 RuntimeError,同时异常链中会保留原来的 ZeroDivisionError。

    这说明清理代码本身也必须尽量可靠。

    例如,不要在清理代码中随意访问可能不存在的对象:

    try:
    connection = create_connection()
    run_query(connection)
    finally:
    connection.close()

    如果 create_connection() 失败,变量 connection 可能根本没有赋值,finally 中又会触发 UnboundLocalError。

    更安全的写法是:

    connection = None

    try:
    connection = create_connection()
    run_query(connection)
    finally:
    if connection is not None:
    connection.close()

    当然,如果资源支持上下文管理器,优先使用 with。


    十三、break 遇到 finally 时的执行顺序

    看一个循环案例:

    for number in range(3):
    try:
    print(f"处理 {number}")

    if number == 1:
    break
    finally:
    print(f"清理 {number}")

    print("循环结束")

    输出:

    处理 0
    清理 0
    处理 1
    清理 1
    循环结束

    当执行 break 时,程序不会立刻跳出循环,而是:

  • 准备跳出循环;
  • 先执行 finally;
  • 再真正结束循环。

  • 十四、continue 遇到 finally 时的执行顺序

    for number in range(3):
    try:
    print(f"开始处理 {number}")

    if number == 1:
    continue

    print(f"完成处理 {number}")
    finally:
    print(f"清理 {number}")

    输出:

    开始处理 0
    完成处理 0
    清理 0
    开始处理 1
    清理 1
    开始处理 2
    完成处理 2
    清理 2

    当 number == 1 时:

  • continue 准备进入下一轮循环;
  • 先执行 finally;
  • 再开始下一次迭代。

  • 十五、嵌套 try 的执行顺序

    复杂项目中可能存在多层异常处理。

    try:
    print("外层 try 开始")

    try:
    print("内层 try 开始")
    result = 10 / 0
    except ZeroDivisionError:
    print("内层 except")
    finally:
    print("内层 finally")

    print("外层 try 继续")
    except Exception:
    print("外层 except")
    finally:
    print("外层 finally")

    输出:

    外层 try 开始
    内层 try 开始
    内层 except
    内层 finally
    外层 try 继续
    外层 finally

    由于内层异常已经被成功处理,外层感知不到异常,因此外层 except 不执行。

    如果内层不处理:

    try:
    print("外层 try 开始")

    try:
    print("内层 try 开始")
    result = 10 / 0
    finally:
    print("内层 finally")

    except ZeroDivisionError:
    print("外层 except")
    finally:
    print("外层 finally")

    输出:

    外层 try 开始
    内层 try 开始
    内层 finally
    外层 except
    外层 finally

    执行顺序是:

  • 内层 try 抛出异常;
  • 内层没有 except;
  • 先执行内层 finally;
  • 异常传播到外层;
  • 外层 except 捕获;
  • 最后执行外层 finally。

  • 十六、多个 except 的匹配顺序

    多个 except 会从上到下依次匹配。

    try:
    number = int("abc")
    except ValueError:
    print("ValueError")
    except Exception:
    print("Exception")

    输出:

    ValueError

    因为 ValueError 是 Exception 的子类,所以应先写具体异常,再写宽泛异常。

    正确:

    try:
    ...
    except FileNotFoundError:
    ...
    except OSError:
    ...
    except Exception:
    ...

    错误:

    try:
    ...
    except Exception:
    print("普通异常")
    except ValueError:
    print("值错误")

    后面的 ValueError 永远没有机会执行,因为它已经被前面的 Exception 捕获。

    Python 通常不会阻止你写这种代码,但它会降低代码质量和可维护性。


    十七、异常处理中的 raise

    有时捕获异常不是为了彻底消化它,而是为了记录信息后继续向上传播。

    try:
    save_order(order)
    except DatabaseError:
    logger.exception("订单保存失败")
    raise

    单独写一个 raise,表示重新抛出当前正在处理的异常。

    这种做法常见于分层系统:

    底层函数发现异常

    当前层记录上下文

    继续抛给上层统一处理

    还可以转换异常类型:

    class OrderSaveError(Exception):
    pass

    def save_order_service(order):
    try:
    repository.save(order)
    except DatabaseError as exc:
    raise OrderSaveError(
    f"订单保存失败,order_id={order.id}"
    ) from exc

    raise … from exc 可以建立清晰的异常链,既保留底层原因,又提供更符合业务语义的信息。


    十八、文件操作实战:正确理解顺序

    先看一种传统写法:

    file = None

    try:
    file = open("data.txt", "r", encoding="utf-8")
    content = file.read()
    except FileNotFoundError:
    print("文件不存在")
    except PermissionError:
    print("没有读取权限")
    else:
    print(f"读取成功,共 {len(content)} 个字符")
    finally:
    if file is not None:
    file.close()
    print("文件已关闭")

    如果文件读取成功,顺序是:

    try → else → finally

    如果文件不存在,顺序是:

    try → except FileNotFoundError → finally

    不过在实际项目中,更推荐使用 with:

    try:
    with open("data.txt", "r", encoding="utf-8") as file:
    content = file.read()
    except FileNotFoundError:
    print("文件不存在")
    except PermissionError:
    print("没有读取权限")
    else:
    print(f"读取成功,共 {len(content)} 个字符")

    with 会自动关闭文件,比手动在 finally 中关闭更加安全、简洁。


    十九、数据库事务实战

    数据库事务是 try/except/else/finally 的经典应用场景。

    connection = create_connection()

    try:
    connection.execute(
    "UPDATE accounts SET balance = balance – 100 WHERE id = 1"
    )
    connection.execute(
    "UPDATE accounts SET balance = balance + 100 WHERE id = 2"
    )
    except DatabaseError:
    connection.rollback()
    logger.exception("转账失败,事务已回滚")
    raise
    else:
    connection.commit()
    print("转账成功,事务已提交")
    finally:
    connection.close()
    print("数据库连接已关闭")

    其设计非常清晰:

    • try:执行数据库操作;
    • except:失败时回滚;
    • else:全部成功后提交;
    • finally:无论成功失败都关闭连接。

    成功时:

    try → else(commit) → finally(close)

    失败时:

    try → except(rollback) → finally(close)

    为什么提交操作放在 else 中,而不是放在 try 最后一行?

    因为这样能缩小 try 的范围,使 except DatabaseError 更准确地对应数据库操作本身。

    不过需要注意,commit() 也可能失败。对于严格系统,可以对提交阶段单独处理:

    try:
    execute_business_sql(connection)
    except DatabaseError:
    connection.rollback()
    raise
    else:
    try:
    connection.commit()
    except DatabaseError:
    connection.rollback()
    raise
    finally:
    connection.close()


    二十、网络请求实战:区分可恢复和不可恢复异常

    假设我们需要调用远程接口:

    def fetch_user_data(client, user_id):
    try:
    response = client.get(
    f"/users/{user_id}",
    timeout=5,
    )
    except TimeoutError:
    logger.warning("请求超时,user_id=%s", user_id)
    return None
    except ConnectionError:
    logger.exception("网络连接失败")
    raise
    else:
    return response.json()
    finally:
    logger.info("用户数据请求结束,user_id=%s", user_id)

    这里的顺序取决于请求结果:

    请求成功:

    try → else → finally

    请求超时:

    try → except TimeoutError → finally → return None

    连接失败:

    try → except ConnectionError → finally → 异常继续传播

    需要特别注意,response.json() 放在 else 中后,它抛出的 JSON 解析异常不会被前面的 except 捕获。

    这既可能是优点,也可能意味着需要单独处理:

    def fetch_user_data(client, user_id):
    try:
    response = client.get(
    f"/users/{user_id}",
    timeout=5,
    )
    except TimeoutError:
    logger.warning("请求超时")
    return None
    else:
    try:
    return response.json()
    except ValueError as exc:
    raise RuntimeError("服务返回了无效 JSON") from exc
    finally:
    logger.info("请求结束")


    二十一、批处理实战:单条失败不影响整体

    在数据导入、自动化脚本和消息消费场景中,经常需要处理大量数据。

    results = []
    failed_items = []

    for item in items:
    try:
    result = process_item(item)
    except ValueError as exc:
    failed_items.append(
    {
    "item": item,
    "reason": str(exc),
    }
    )
    except Exception:
    logger.exception("处理项目时发生未预期异常:%r", item)
    failed_items.append(
    {
    "item": item,
    "reason": "系统内部错误",
    }
    )
    else:
    results.append(result)
    finally:
    release_item_resource(item)

    对于每一条数据:

    成功时:

    try → else → finally

    失败时:

    try → except → finally

    这种结构非常适合“局部失败、整体继续”的任务。

    但需要考虑几个工程问题:

    • 失败数据是否需要重试?
    • 是否存在重复执行风险?
    • 是否应该进入死信队列?
    • 是否要限制失败数量?
    • 连续大量失败时是否应终止任务?
    • 日志中是否包含足够的业务标识?

    异常处理不仅是语法问题,更是系统恢复策略的一部分。


    二十二、异步编程中的执行顺序

    在 asyncio 中,try/except/else/finally 的基本执行顺序没有变化。

    import asyncio

    async def fetch_data():
    await asyncio.sleep(1)
    return {"status": "ok"}

    async def main():
    try:
    data = await fetch_data()
    except asyncio.TimeoutError:
    print("请求超时")
    else:
    print(f"请求成功:{data}")
    finally:
    print("异步任务结束")

    asyncio.run(main())

    成功时依然是:

    try → else → finally

    异步任务取消时,应特别谨慎。

    async def worker():
    try:
    while True:
    await asyncio.sleep(1)
    except asyncio.CancelledError:
    print("任务收到取消信号")
    raise
    finally:
    print("释放异步资源")

    这里重新抛出 CancelledError 很重要,它让任务保持正确的取消语义。

    不要这样写:

    async def worker():
    try:
    await run_forever()
    except Exception:
    pass

    宽泛捕获可能让异步任务悄悄失败,增加排查难度。


    二十三、哪些情况下 finally 可能不执行?

    我们通常说 finally 一定执行,但严谨地说,它是“几乎总会执行”。

    以下极端情况下,finally 可能没有机会运行:

    1. 进程被强制终止

    例如操作系统直接杀死进程。

    2. 调用了强制退出接口

    某些底层退出方式会跳过常规清理流程。

    3. 机器断电或解释器崩溃

    程序无法继续执行任何 Python 代码。

    4. finally 代码根本没有被执行到

    例如程序在进入对应 try 结构前就已经终止。

    因此,不应把绝对关键的数据持久化完全寄托在 finally 上。对重要系统,还需要事务、日志、幂等、恢复机制和外部监控。


    二十四、常见错误一:try 范围太大

    不推荐:

    try:
    config = load_config()
    user = load_user()
    amount = int(user["amount"])
    result = calculate(config, amount)
    save_result(result)
    except ValueError:
    print("金额格式错误")

    问题是,任何函数内部的 ValueError 都可能被误判为金额格式错误。

    推荐:

    config = load_config()
    user = load_user()

    try:
    amount = int(user["amount"])
    except ValueError as exc:
    raise ValueError("用户金额必须为整数") from exc

    result = calculate(config, amount)
    save_result(result)

    或者:

    try:
    amount = int(user["amount"])
    except ValueError as exc:
    raise ValueError("用户金额必须为整数") from exc
    else:
    result = calculate(config, amount)
    save_result(result)


    二十五、常见错误二:在 finally 中改变控制流

    不推荐:

    def process():
    try:
    return do_work()
    finally:
    return None

    它会覆盖真实结果,甚至吞掉异常。

    也不推荐在 finally 中随意使用:

    break
    continue
    return
    raise

    因为这些语句可能覆盖原有控制流。

    推荐让 finally 保持单一职责:

    def process():
    resource = acquire_resource()

    try:
    return do_work(resource)
    finally:
    resource.close()


    二十六、常见错误三:捕获异常后什么也不做

    try:
    save_data()
    except Exception:
    pass

    这种写法会让失败变得不可见。

    至少应记录日志:

    try:
    save_data()
    except Exception:
    logger.exception("数据保存失败")
    raise

    对于确实允许忽略的异常,也应说明原因:

    try:
    remove_temp_file(path)
    except FileNotFoundError:
    # 临时文件已不存在,目标状态已经满足
    pass

    这里忽略 FileNotFoundError 是合理的,因为“文件不存在”本身就是期望的最终状态。


    二十七、常见错误四:使用裸 except:

    try:
    do_something()
    except:
    print("失败")

    裸 except: 会捕获包括 KeyboardInterrupt、SystemExit 在内的特殊异常。

    更推荐捕获具体异常:

    try:
    do_something()
    except ValueError:
    ...
    except OSError:
    ...

    确实需要捕获普通异常时,可以使用:

    try:
    do_something()
    except Exception:
    logger.exception("未预期异常")
    raise

    异常处理的原则不是捕获得越多越安全,而是:

    只捕获当前代码真正知道如何处理的异常。


    二十八、推荐的代码模板

    模板一:处理可预期异常

    try:
    value = int(user_input)
    except ValueError:
    print("请输入有效整数")
    else:
    print(f"输入成功:{value}")


    模板二:记录后继续抛出

    try:
    perform_operation()
    except SpecificError:
    logger.exception("操作失败")
    raise


    模板三:资源清理

    resource = acquire_resource()

    try:
    use_resource(resource)
    finally:
    release_resource(resource)

    更推荐:

    with acquire_resource() as resource:
    use_resource(resource)


    模板四:事务处理

    connection = create_connection()

    try:
    execute_transaction(connection)
    except DatabaseError:
    connection.rollback()
    raise
    else:
    connection.commit()
    finally:
    connection.close()


    模板五:批量处理

    for item in items:
    try:
    result = process(item)
    except KnownError as exc:
    logger.warning("处理失败:%s", exc)
    except Exception:
    logger.exception("未预期异常")
    else:
    save_success(result)
    finally:
    cleanup(item)


    二十九、执行顺序速查表

    场景执行顺序
    try 正常完成 try → else → finally
    try 抛出异常并被捕获 try → except → finally
    try 抛出异常但未被捕获 try → finally → 异常向外传播
    except 中再次抛出异常 try → except → finally → 新异常向外传播
    else 中抛出异常 try → else → finally → 异常向外传播
    try 中执行 return try → finally → return
    except 中执行 return try → except → finally → return
    try 中执行 break try → finally → break
    try 中执行 continue try → finally → continue
    finally 中执行 return 覆盖原返回值,可能吞掉异常
    finally 中抛出新异常 新异常通常成为最终传播异常

    三十、一段代码测试全部执行路径

    下面这段代码适合自己动手实验:

    def test_flow(mode):
    print(f"\\n— mode={mode} —")

    try:
    print("1. 进入 try")

    if mode == "value_error":
    raise ValueError("值错误")

    if mode == "type_error":
    raise TypeError("类型错误")

    if mode == "return":
    print("2. try 准备 return")
    return "try result"

    print("2. try 正常结束")

    except ValueError as exc:
    print(f"3. 捕获 ValueError:{exc}")

    else:
    print("4. 进入 else")

    finally:
    print("5. 进入 finally")

    print("6. 异常结构之后")
    return "normal result"

    调用:

    print(test_flow("normal"))
    print(test_flow("value_error"))
    print(test_flow("return"))

    第一种情况:

    try → else → finally → 后续代码

    第二种情况:

    try → except → finally → 后续代码

    第三种情况:

    try 准备 return → finally → 真正 return

    如果调用:

    print(test_flow("type_error"))

    因为没有对应的 except TypeError:

    try → finally → TypeError 向外传播

    动手运行这些代码,比死记规则更容易形成稳定理解。


    三十一、工程中的最佳实践

    1. 优先捕获具体异常

    except FileNotFoundError:

    通常比:

    except Exception:

    更清晰。


    2. 尽量缩小 try 范围

    只把真正可能抛出目标异常的代码放入 try。


    3. 用 else 放置成功后的逻辑

    这样可以避免误捕获后续操作中的异常。


    4. 用 finally 做清理,不做业务决策

    finally 适合:

    close()
    release()
    unlock()
    cleanup()

    不适合:

    return
    break
    continue


    5. 优先使用上下文管理器

    with open(...) as file:

    通常比手动 try/finally 更安全。


    6. 不要静默吞掉异常

    至少记录错误和关键业务上下文:

    logger.exception(
    "订单处理失败,order_id=%s",
    order_id,
    )


    7. 转换异常时保留异常链

    raise BusinessError("业务处理失败") from exc


    8. 为异常处理编写测试

    可以使用 pytest:

    import pytest

    def divide(a, b):
    if b == 0:
    raise ValueError("除数不能为 0")

    return a / b

    def test_divide_zero():
    with pytest.raises(ValueError, match="除数不能为 0"):
    divide(10, 0)

    还应测试清理逻辑是否执行:

    def test_resource_is_closed():
    resource = FakeResource()

    try:
    use_resource(resource)
    finally:
    resource.close()

    assert resource.closed is True


    三十二、总结

    try/except/else/finally 的执行顺序并不复杂,真正困难的是在复杂控制流中保持代码清晰。

    最核心的三条规则是:

    没有异常:try → else → finally
    异常被捕获:try → except → finally
    异常未捕获:try → finally → 向外传播

    还需要牢记几个关键细节:

    • try 发生异常后,剩余代码不会继续执行;
    • else 只在 try 完整成功时执行;
    • else 中的异常不会被前面的 except 捕获;
    • finally 通常会在 return、break、continue 和异常传播前执行;
    • finally 中的 return 可能覆盖结果并吞掉异常;
    • finally 应专注资源清理,而不是改变程序控制流;
    • try 范围越小,异常处理通常越准确;
    • 只捕获自己真正能够处理的异常。

    好的异常处理,不是让程序看起来“永远不出错”,而是让程序在出现问题时,仍然能够安全释放资源、保留错误信息、维持数据一致性,并为后续排查提供足够线索。

    当你真正理解这四个关键字的执行顺序后,文件操作、数据库事务、异步任务、批量处理和接口调用都会变得更加可靠。

    下一次写下 try 时,不妨先问自己:

    • 这里真正可能发生什么异常?
    • 我是否应该捕获它?
    • 成功逻辑是否适合放进 else?
    • 哪些资源必须在 finally 中释放?
    • 当前代码会不会无意中吞掉错误?

    这些看似简单的问题,往往正是普通脚本与高质量 Python 工程之间的分界线。

    你是否遇到过因为 finally 中写了 return,导致异常被悄悄吞掉的情况?在数据库事务、异步任务或批处理系统中,你又是如何设计异常恢复机制的?欢迎分享你的经验与思考,让我们一起把 Python 代码写得更清晰、更可靠。

    赞(0)
    未经允许不得转载:171主机测评 » 一文吃透 Python 异常处理:`try/except/else/finally` 的执行顺序、陷阱与实战
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址