码农高天
try finally的资源清理保障仅限于解释器捕获的异常体系,一旦遭遇os._exit()、系统强杀信号或底层段错误,进程会被操作系统直接瓦解而绕过所有释放逻辑。
AI-assisted summary of 【python】学会用try finally优雅地释放资源,再了解它啥时候不好使 — original source. Check the original for full context.
Source published:
Key ideas
- Python 中的 finally 块具有无论 try 内部是否抛出异常都会强制执行的确定性,非常适合用来安全关闭网络连接、文件句柄及子进程等系统资源。 (0:00)
- 相较于 atexit 模块仅在进程退出时触发回调,try...finally 能精确控制局部作用域的即时释放,但在库级封装时 atexit 能提供对用户透明的全局清理能力。 (3:00)
- try...finally 的保障机制完全受限于 Python 解释器异常体系,面对操作系统层面的 SIGTERM、SIGKILL(kill -9)、底层 os._exit() 以及 C 扩展段错误时将彻底失效。 (5:58)
Detailed notes
## Background 该视频由技术博主“码农高天”于2024年4月10日发布在YouTube平台上。视频主要探讨了如何在Python中利用try...finally语法优雅地释放系统资源,并剖析了这一机制在哪些特殊情况下会彻底失效。
## TL;DR 视频首先讲解了try...finally的核心机制,指出无论try块中是否发生异常,finally内的代码都会无条件执行,是释放文件句柄、网络连接及子进程等资源的利器。接着对比了finally与atexit模块的异同,说明前者适合局部作用域的即时清理,而后者更适合在库级封装中管理全局生命周期。最后,高天深入到底层与操作系统视角,系统盘点了finally的边界与失效场景:虽然sys.exit()和Ctrl+C(SIGINT)仍能正常触发finally,但遇到SIGTERM、SIGKILL(kill -9)、底层系统调用os._exit()或段错误(SegFault)时,finally均无法执行。
## Atmosphere & style 视频整体呈现出清晰、严谨且务实的技术分享氛围。高天的讲解层层递进,从大家熟知的日常语法切入,平稳过渡到系统级信号和底层运行时边界,逻辑衔接极为顺畅。观众在跟随代码示例与机制剖析的过程中,既能建立对资源管理的踏实信心,又能在直面语言底层盲区时获得豁然开朗的启发感。
## Bulletin - [00:00](https://www.youtube.com/watch?v=lmhFLrdGQjo&t=0s) Python 中的 finally 块具有无论 try 内部是否抛出异常都会强制执行的确定性,非常适合用来安全关闭网络连接、文件句柄及子进程等系统资源。 - [03:00](https://www.youtube.com/watch?v=lmhFLrdGQjo&t=180s) 相较于 atexit 模块仅在进程退出时触发回调,try...finally 能精确控制局部作用域的即时释放,但在库级封装时 atexit 能提供对用户透明的全局清理能力。 - [05:58](https://www.youtube.com/watch?v=lmhFLrdGQjo&t=358s) try...finally 的保障机制完全受限于 Python 解释器异常体系,面对操作系统层面的 SIGTERM、SIGKILL(kill -9)、底层 os._exit() 以及 C 扩展段错误时将彻底失效。
## Key takeaways - [00:00](https://www.youtube.com/watch?v=lmhFLrdGQjo&t=0s) finally 的核心语义在于无论 try 块内是否产生异常、异常是否被 except 捕获,其包含的逻辑均会被 Python 解释器调度执行。 - [00:00](https://www.youtube.com/watch?v=lmhFLrdGQjo&t=0s) 传统线性编写的清理代码容易因中途报错而被跳过,使用 try...finally 能够有效防止网络 Socket、数据库连接池及文件描述符发生资源泄漏。 - [00:00](https://www.youtube.com/watch?v=lmhFLrdGQjo&t=0s) 即使程序发生未捕获的严重错误(如除以零)导致程序最终崩溃退出,finally 中的清理代码仍能确保被顺利调用。 - [03:00](https://www.youtube.com/watch?v=lmhFLrdGQjo&t=180s) atexit 模块只能在整个 Python 程序完全退出时触发回调,无法满足单次网络请求等局部生命周期的即时清理需求。 - [03:00](https://www.youtube.com/watch?v=lmhFLrdGQjo&t=180s) try...finally 具备细粒度的局部作用域控制能力,能够随着代码脱离局部范围立即触发资源释放逻辑。 - [03:00](https://www.youtube.com/watch?v=lmhFLrdGQjo&t=180s) 在第三方库设计中,强制使用 try...finally 会要求用户在最外层显式包裹代码,而 atexit 可实现初始化即注册的零侵入式体验。 - [05:58](https://www.youtube.com/watch?v=lmhFLrdGQjo&t=358s) sys.exit() 在底层本质上是抛出 SystemExit 异常,属于 Python 解释器异常体系,因此能正常触发 finally 块执行。 - [05:58](https://www.youtube.com/watch?v=lmhFLrdGQjo&t=358s) 终端按下 Ctrl+C 会向进程发送 SIGINT 并转化为 KeyboardInterrupt 异常,finally 依然能够拦截并完成清理。 - [05:58](https://www.youtube.com/watch?v=lmhFLrdGQjo&t=358s) 若未编写专门的信号处理函数,进程接收到默认的终止信号 SIGTERM 时会被操作系统直接结束,try...finally 不会执行。 - [05:58](https://www.youtube.com/watch?v=lmhFLrdGQjo&t=358s) 操作系统的强制杀死信号 SIGKILL(kill -9)由内核直接抹除进程,任何应用层的清理逻辑均无法运行。 - [05:58](https://www.youtube.com/watch?v=lmhFLrdGQjo&t=358s) os._exit() 会直接调用操作系统底层系统调用退出进程,绕过 Python 解释器的异常与垃圾回收机制,导致 finally 被完全跳过。 - [05:58](https://www.youtube.com/watch?v=lmhFLrdGQjo&t=358s) C 扩展引起的内存越界段错误(SegFault)会直接摧毁 Python 运行时环境,使 try...finally 丧失执行基础。
## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=lmhFLrdGQjo&t=0s) try...finally 的基本原理与资源安全释放
Python 开发者通常很熟悉 `try...except` 语法,大多用它来捕获并处理潜在异常(例如发生除以零错误时捕获异常并打印日志,避免整个程序意外崩溃退出)。但在与 `try` 相关的语法结构中,除了 `except` 和没有异常时执行的 `else` 之外,还有一个关键关键字——`finally`。`finally` 的核心语义在于:在 `try` 代码块执行完毕后,无论其内部是否抛出异常,Python 都会无条件执行 `finally` 块中的逻辑。这种“无论如何都会运行”的确定性特性,使其成为管理和释放资源的利器。在日常开发中,程序经常需要申请网络连接、数据库连接、文件句柄或子进程等必须被妥善释放的系统资源;若代码在申请资源后中途抛出异常,原本写在后面的清理逻辑就会被跳过,导致资源泄漏。
```mermaid flowchart TD accTitle: try...finally 资源释放执行流 accDescr: 展示在有无异常情况下 try...finally 均能保证资源释放代码被调用的逻辑走向
subgraph NormalFlow["正常执行流程"] A["Resource Acquire (获取资源)"] --> B["业务逻辑代码"] B --> C["finally 块: Resource Release (释放资源)"] end
subgraph ErrorFlow["发生异常流程"] D["Resource Acquire (获取资源)"] --> E["业务逻辑报错 (例如 1/0)"] E --> F{"是否由 except 捕获?"} F -->|"是"| G["打印错误/处理异常"] F -->|"否"| H["未捕获异常 (程序最终崩溃)"] G --> I["finally 块: Resource Release (依然释放)"] H --> I end ```
通过简单的打印示例可以清晰观察到这一机制的优势:如果仅按顺序编写“获取资源 -> 运行代码 -> 释放资源”,一旦中间代码报错,最后的释放语句就永远无法执行。而一旦将业务逻辑包裹在 `try` 中、将释放代码置于 `finally` 块内,即使程序没有捕获除以零的异常并最终崩溃退出,终端中依然能看到释放资源的代码被顺利调用。即使开发者显式使用 `except` 捕获了异常,`finally` 块依然会稳定执行。码农高天强调:“你可以理解成无论Try里面发生了什么finally里面的代码都会运行而finally的这个无论如何都要运行的特性就经常被用在资源的释放上。”
- `try...finally` 与 `try...except...finally` 都能确保清理代码被执行,无论异常是否被捕获。 - 适用场景包括关闭网络 Socket、释放数据库连接池连接、关闭打开的文件描述符以及终止开启的子进程。
---
[03:00](https://www.youtube.com/watch?v=lmhFLrdGQjo&t=180s) try...finally 与 atexit 模块的对比与权衡
针对资源释放,Python 标准库中还提供了 `atexit` 模块,二者在设计定位与适用场景上各有优劣。`atexit` 顾名思义只能在整个 Python 程序退出时触发回调,而 `try...finally` 则具有高度的灵活性,可以在任意作用域(Scope)下运行。例如在处理网络请求的服务中,如果每次收到请求都需要启动一个子进程,并要求无论请求处理成功还是报错,在该请求生命周期结束时必须立即杀掉该子进程,此时 `try...finally` 显然是更好的选择,而 `atexit` 则无法满足这种局部作用域的即时清理需求。
| 对比维度 | `try...finally` | `atexit` 模块 | |:--- |:--- |:--- | | **触发时机** | 离开当前局部作用域(Scope)后立即执行 | 仅在整个 Python 进程终止退出时执行 | | **颗粒度** | 精细控制局部上下文(如单次函数调用、单次 HTTP 请求) | 全局生命周期管理 | | **代码侵入性** | 具有侵入性,调用方通常必须在控制流最外层显式包裹语法块 | 零侵入,库作者可在初始化时静默注册,用户无感知 | | **封装友好度** | 若库底层持有全局常驻资源,要求用户手动写 `try...finally` 体验较差 | 适合库级抽象,避免污染用户代码 |
从代码架构与封装角度来看,`try...finally` 必须在执行流程中显式书写。如果开发者编写了一个第三方库,在模块加载或初始化时启动了一个与主进程通信的子进程,并希望在主进程结束时自动关掉子进程,如果强制采用 `try...finally`,就必须要求库的使用者在其业务代码的最外层手动套上该结构,这给调用者带来了额外的认知负担与繁琐操作。码农高天对此分析道:“然而如果用 Add Exit这个module你可以在你的酷寒数里面申请这个资源的同时注册一下资源的释放函数这样用户在使用你这个库的时候 他完全不用担心资源释放的问题他不需要去理解你的库也不需要显示的在自己的代码里面增加一些释放资源的代码。”
---
[05:58](https://www.youtube.com/watch?v=lmhFLrdGQjo&t=358s) 边界与失效场景:操作系统级信号与底层崩溃
尽管 `finally` 极具保障性,但它并非在任何情况下都能优雅执行。最极端也是显而易见的情况是直接拔掉计算机电源,此时任何软件层面的清理代码都不可能被运行。究其根本,`try...finally` 属于纯粹的 Python 语言层面概念,它能够保证运行的前提是:程序中断或退出属于 Python 解释器能够感知并调度的异常体系。
- **`sys.exit()`:** 其底层本质是引发了一个 `SystemExit` 异常,属于 Python 异常体系的一部分,因此 `finally` 会被正常触发执行。 - **键盘中断 `Ctrl+C` 与 `SIGINT`:** 在终端中按下 `Ctrl+C` 本质上是向 Python 进程抛出了 `KeyboardInterrupt` 异常,等价于接收到 `SIGINT` 信号,`finally` 能够拦截并顺利打印释放信息。 - **终止信号 `SIGTERM`:** 当进程收到非捕获处理的 `SIGTERM` 信号时,进程会被操作系统直接终止(Terminate),纯粹的 `try...finally` 结构面对 `SIGTERM` 完全无能为力,清理代码不会执行(除非在 Python 中专门编写了针对 `SIGTERM` 的信号处理函数)。 - **强制终止信号 `SIGKILL`:** 作为操作系统强制杀进程的手段(`kill -9`),进程会被内核瞬间抹去,`finally` 绝无运行可能。 - **底层系统调用 `os._exit()`:** 与 `sys.exit()` 不同,`os._exit()` 极其底层,会直接发起底层系统调用立即退出进程,完全绕过解释器的垃圾回收和异常机制,`finally` 代码块直接被跳过。 - **段错误(Segmentation Fault / SegFault):** 当 C 扩展或底层运行时发生内存访问越界导致段错误崩溃时,Python 解释器自身直接瓦解,`finally` 同样无法执行。
在实际工程中认清这些边界能够避免盲目信赖语言特性。码农高天总结表示:“总结一下从python的层面看tryfinally依然是一个非常优秀的保证释放资源的手段它的形式简单一懂好写同时覆盖了大部分我们会遇到的可能性当然没有一个方法是完美的try finally也有它触及不到的情况对于这些情况如果我们心里有个数也会对我们的编程有所帮助”。
## Notable quotes > [00:00](https://www.youtube.com/watch?v=lmhFLrdGQjo&t=0s) 你可以理解成无论Try里面发生了什么finally里面的代码都会运行而finally的这个无论如何都要运行的特性就经常被用在资源的释放上。 — 码农高天
## Mentioned Python, 码农高天, atexit, sys.exit(), os._exit(), SystemExit, KeyboardInterrupt, SIGINT, SIGTERM, SIGKILL
## Video description 这期视频我们聊一下如何使用try finally来优雅地释放资源,以及这种语法在什么情况下会无法工作。
[Source](https://www.youtube.com/watch?v=lmhFLrdGQjo)