找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

5369

积分

0

好友

730

主题
发表于 1 小时前 | 查看: 3| 回复: 0

在 Python 开发中,try/finally 是常见的资源清理模式,但其中隐藏的陷阱却常被忽略。今天,云栈社区 就来剖析这个让无数人踩过的坑。

先看一个例子:

try-finally代码示例:send_message函数中finally覆盖异常返回值

运行结果令人意外:它会显示 finally返回,而不是 try 块中的返回值。

终端输出结果:finally返回

场景:如何操作关闭文件资源/临时文件/数据库等?

当你处理外部资源(如数据库或临时文件)时,通常需要在代码完成后,执行某些清理操作,比如 file.close()connect.close()

Python 提供了两种常用的操作方法:上下文管理器 (with 语法) 和 try/finally 代码块。两者都是有效的方案,但上下文管理器通常被赞誉为更具 Python 风格的代码。

尽管如此,try/finally 块还是被广泛使用(可能是其他语言的惯性带过来的吧)。try/finally 简单易用且在某些情况下非常合适,但它隐藏着避坑指南中反复警示的陷阱。

try/finally 基础逻辑

try/finally 模式的变体存在于大多数语言中,其运行方式和你想象的是一样的。简单的例子:

def walk_path(): 
  try:
    print("第一步")
  finally:
    print("第二步")

# 执行时打印内容:
# 第一步
# 第二步

代码进入第一个代码块并执行语句,最后再进入 finally 块。finally 块总是会执行,无论第一个块是成功还是失败。

如果执行过程中抛出了错误(如下所示),错误会中断正常流程,解释器立即停止执行 try 块,并直接跳到 finally 块。

def walk_path():
  try:
    raise Exception("发出异常")
  finally:
    print("第二步")

如果你选择使用 except 块来处理错误,错误会在进入 finally 块之前被 except 块捕获并处理。即便在 except 内部发生了错误,解释器依然会在抛出新错误之前执行 finally 块。

也就是 finally 块无论如何都会被运行。

finally 的陷阱

这要从函数返回过程说起了。函数可以在 try 块内部返回一个值。当解释器遇到 return 语句时,它准备将此值传回调用者。然而,如果它后面还有 finally 块:它会暂停返回过程,先执行 finally 块,然后再返回 try 块中准备好的值。

然而如果 finally 块里也有 return,这时候你能想象会发生什么事情吗?

def walk_path(): 
       try: 
           return "到达目的地" 
       finally: 
           return "返回家中" 
# 这时候会返回“返回家中”

当这种情况发生时,finally 块中的 return 会胜出,而来自 try 块的返回值会被有效地忽略。

这样的行为同样适用于发生异常时,如果 try 块里返回的异常,但是 finallyreturn 时,那么异常并不会抛出给上级函数。

finally 的问题:被吞掉异常

try 块和 finally 块都存在 return 时,代码虽然都会执行,但是 try 块的 return 永远都不会 return,因为 return 的结果被 finally 覆盖了。

这样导致最大的问题就是:当代码报异常时,异常其实也算是一种返回值,finally 里的 return 会把这个异常给吞了,变成 finally 里的返回值

任何允许你编写会产生意外结果的“死代码”的语言都是有问题的。Python 的开发者们意识到了这个问题,并在 PEP 601 提议中提议禁止在 finally 块中使用 return/break/continuebreak/continue 也和 return 有一样的效果,会篡改返回值)。但是该提议被否决了。

阅读 PEP 中的参考文献后,我认为大多数语言都实现了这种结构,但它们的风格指南和/或代码检查工具却不接受它。

我支持将此内容添加到 PEP 8 中的提案(如果尚未添加的话)。

我注意到这些玩具示例有点误导性——真正有用的功能是在 finally 代码块内使用条件返回(或 break 等)。

-Guido 2019 PEP601

Guido 的理由是,在某些合法场景中,用户可能需要完全控制 finally 块中的异常处理,并希望覆盖异常的抛出。阻止这种行为会限制高级用户。

但是 2024 年这个问题又被提出来了:

开发者们掌握了证据。他们分析了 PyPI 排名前 8000 的软件包,发现:

finally 中使用 return 的绝大多数情况都是错误的,并且引入了意外吞掉异常的 bug。 - PEP765

于是该提案被通过了,从 Python 3.14 开始,在 finally 中使用 returnbreakcontinue 时会报一个 SyntaxWarning 的异常。

在 Python 3.14 以前,由于 finally 里可以使用 returncontinuebreak 等操作,会导致原本应该正常抛出的异常,被 finally 的返回值顶替。

Python 3.14 官方更新文档:

PEP 765 finally控制流警告说明

代码语言设计的逻辑上没问题,不代表不需要人为干预。finally 块的逻辑上不存在任何问题,只是人们太容易忽略这个问题且出错了,所以 Python 开发者们通过分析现有项目的使用情况就大胆禁用了 finallyreturn 的语法。而反观其他语言如 Java,到现在都只有人为的规范,并没有强制限制这个特性。这可能就是 Python 越来越好用的原因吧。

部分翻译自 substack 文章,原文放原文链接里了。




上一篇:Oracle RAC安装配置:Linux与Windows实践要点
下一篇:子查询改 JOIN 优化实战:从性能灾难到百万 QPS 稳定
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-5-13 17:53 , Processed in 0.714742 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表