Python try finally难防系统级终止 遇SIGKILL与崩溃直接失效

【python】学会用try finally优雅地释放资源,再了解它啥时候不好使 — 码农高天

码农高天

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)

中文简报

Python try finally难防系统级终止 遇SIGKILL与崩溃直接失效 try finally的资源清理保障仅限于解释器捕获的异常体系,一旦遭遇os._exit()、系统强杀信号或底层段错误,进程会被操作系统直接瓦解而绕过所有释放逻辑。 视频首先讲解了try...finally的核心机制,指出无论try块中是否发生异常,finally内的代码都会无条件执行,是释放文件句柄、网络连接及子进程等资源的利器。接着对比了finally与atexit模块的异同,说明前者适合局部作用域的即时清理,而后者更适合在库级封装中管理全局生命周期。最后,高天深入到底层与操作系统视角,系统盘点了finally的边界与失效场景:虽然sys.exit()和Ctrl+C(SIGINT)仍能正常触发finally,但遇到SIGTERM、SIGKILL(kill -9)、底层系统调用os._exit()或段错误(SegFault)时,finally均无法执行。 Python 中的 finally 块具有无论 try 内部是否抛出异常都会强制执行的确定性,非常适合用来安全关闭网络连接、文件句柄及子进程等系统资源。 (0:00) 相较于 atexit 模块仅在进程退出时触发回调,try...finally 能精确控制局部作用域的即时释放,但在库级封装时 atexit 能提供对用户透明的全局清理能力。 (3:00) try...finally 的保障机制完全受限于 Python 解释器异常体系,面对操作系统层面的 SIGTERM、SIGKILL(kill -9)、底层 os._exit() 以及 C 扩展段错误时将彻底失效。 (5:58)

Reading summary

码农高天指出,Python 中的 `try...finally` 结构具备“无论如何都会运行”的确定性,是安全释放资源的核心手段,但其有效性完全受制于 Python 解释器的感知边界。 try...finally 的执行确定性与资源清理 (0:00) 在 Python 编程中,申请网络连接、数据库连接池、文件句柄或子进程后,若业务代码因抛出异常而中断,顺序编写的清理代码往往会被跳过,进而引发资源泄漏。`try...finally` 结构的核心语义在于:无论 `try` 块内是否报错、报错是否被 `except` 捕获,甚至未捕获异常导致程序最终崩溃,`finally` 块内的清理逻辑都会被解释器无条件执行。 这一机制使开发者能够将获取资源与释放资源的动作解耦,确保哪怕业务逻辑出现除以零等不可预知的异常崩溃,底层系统资源的释放语句依然能被稳定触发,从而避免了手动错误处理中容易遗漏的清理盲区。 局部作用域控制与 atexit 机制的权衡 (3:00) 针对资源释放,Python 标准库还提供了 `atexit` 模块。二者的核心差异在于作用域粒度与调用侵入性:`try...finally` 作用于局部上下文,适合管理单次函数调用或单次 HTTP 请求期间派生的临时子进程,离开局部作用域后即可立即清理;而 `atexit` 仅在整个 Python 进程终止退出时才会触发回调,无法实现局部资源的即时释放。 但在软件架构与封装设计上,`atexit` 具备零侵入优势。如果第三方库在初始化时持有了常驻全局资源,并在主进程结束时需要清理,使用 `atexit` 可在底层静默注册,避免强制要求用户在其业务代码最外层显式包裹 `try...finally`,从而减轻了使用者的认知负担。 系统级信号与底层崩溃导致的失效边界 (5:58) 码农高天强调,`try...finally` 属于纯粹的 Python 语言级特性,其保障前提是程序中断处于解释器可调度的异常体系之内。例如 `sys.exit()` 触发的是 `SystemExit` 异常,终端按下 `Ctrl+C` 触发的是 `KeyboardInterrupt` 异常(对应 `SIGINT`),这些情况下 `finally` 均能正常拦截并执行。 然而,当面对操作系统层面的强行干预或底层严重错误时,`finally` 会完全失效。例如未作处理的 `SIGTERM` 信号会直接终止进程,`SIGKILL`(`kill -9`)会由内核强制抹除进程,底层系统调用 `os._exit()` 会跳过解释器垃圾回收与异常流程直接退出,C 扩展导致的段错误(SegFault)更会使解释器自身瓦解。在这些场景下,`finally` 块均无法得到执行机会。 总体而言,`try...finally` 凭借简明形式覆盖了日常开发中的绝大多数资源管理场景,但开发者必须清楚其触及不到的底层信号与系统级边界,在设计高可靠性程序时防范超出解释器控制范围的非正常退出。

Source quotes

[0:00] 码农高天: 你可以理解成无论Try里面发生了什么finally里面的代码都会运行而finally的这个无论如何都要运行的特性就经常被用在资源的释放上。

Original transcript

[0:00] 这期视频我们来讲一下Python这个tryfinally语法的应用那我相信大家可能都使用过Try这个语法更多的时候我们是用它来处理一些可能出现的异常比如说我们做一个1除以0python会给你报错那这个时候我们可以用try accept来规定python当遇到这种异常的时候你应该干什么那我现在的代码呢就是说在这种异常的时候你只需要打印一下就可以了不要给我退出整个程序这个比较常见的try accept的用法并不是我们今天的主角在python的try有关的语法里除了except之外我们还可以用else跟finallyelse 就是没有出现异常的时候应该怎么办 也不是我们今天的重点我们今天主要来讲finallyfinally的意思就是try里面的这个代码块运行之后无论里面是否抛出异常Python都会运行finally里面的代码块你可以理解成无论Try里面发生了什么finally里面的代码都会运行而finally的这个无论如何都要运行的特性就经常被用在资源的释放上我们知道在我们写程序的时候经常会拿到一些必须释放的资源比如说它可能是一个网络的链接跟数据库的链接比如说它可能是一个打开的文件还比如说它可能是一个我需要关闭的紫禁城那有的时候在我们申请了这些资源之后由于我的代码报错所以我用来释放这些资源的代码没能运行导致这些资源没能被正确的释放我们这里用两个简单的print来表示抽象的资源的获取和释放当我们一段程序正常的运行的时候我们可能是获取资源然后运行这代码然后释放资源但假如说我们的代码中间有错误的话可以看到我们在获取了这个资源之后由于代码报错了所以我们释放资源的这一行命令没有被执行那如果我们把资源的获取和运行的代码放到try里面然后把资源的释放放到finally里我们可以看到尽管最终Python的程序还是以异常结束了我们并没有捕获这个异常但是我们的资源释放的部分还是被运行了这个Resource Acquire和Release都被打印出来了同时 即便我们尝试捕获了这个异常就像刚才一样打印一行说你除以零了这个finally的代码块依然会被运行我们看到Resource Release依然被打印出来了因此我们可以试想当我们获取了一个必须要释放的资源而在获取资源之后又要运行一大段有可能会报错的代码的时候这个try finally会让我们的资源释放变得更加的稳定好,那有人可能会问了,说你在之前的视频里面提到过一个module,叫做add exitAddExit Module也经常被用来释放资源那么, try finally 跟 add access 相比,在释放资源这个领域孰优孰劣呢那这种问题的答案必然是他们各有优劣对吧an exit module 顾名思义它的释放资源只能在程序结束的时候运行而try finally可以在任意的scope下运行比如说我每接到一个请求需要做一件事情,而这个事情会新建一个紫禁城。我需要确认这个紫禁城在完成这个请求之后被kill掉了无论在完成请求的过程中出现了什么幺蛾子这个时候明显用try finally就会更好它可以保证在一段代码运行之后你的这个资源释放函数就一定会被运行这个事情就做不到那从另外一个角度讲try finally必须要在你的代码运行流程上显示的加入代码比如说我写了一个module这个module在开始的时候会新建一个紫禁城然后这个紫禁城跟你的主进城之间可能有些 [5:00] 就能做到工作你只需要保证在我这个主进程结束的时候记得把紫禁城关掉就可以了那这种情况下如果你用tryfinally来做的话你就必须要保证用户要在他自己的代码最外层写上这个tryfinally因为你资源的释放需要等到这个用户不再使用你这个module之后再释放这就给用户带来了很多麻烦然而如果用 Add Exit这个module你可以在你的酷寒数里面申请这个资源的同时注册一下资源的释放函数这样用户在使用你这个库的时候 他完全不用担心资源释放的问题他不需要去理解你的库也不需要显示的在自己的代码里面增加一些释放资源的代码好 接下来有一个非常严肃的问题你刚才说finally里面的代码无论如何都会被执行真的是这样吗他真的可以在任何情况下都优雅的释放资源吗答案显然是不是 对吧我们都知道如果你把电脑电源拔了这个finally是一定不会运行的所以它能覆盖的肯定是某一个范围那么这个范围能覆盖到哪儿或者说什么样的问题有可能会导致finally无法执行我们知道try finally它显然是一个python层面的概念所以他肯定只能解决python层面出现的问题比如说当我们使用之前介绍过的sys.exit 0的时候你可以看到这个finally是被运行了的我们之前也提到过,sist their exit 本质上是raise 的一个exception这种情况finally是完全可以处理的同样的当我们用Ctrl+C退出python程序的时候finally也可以处理因为当我们输入Ctrl+C的时候,它本质上是raise了一个keyboard interrupt也是一个exception我们可以看到在这里resource release也被打印出来了那我们知道在python里面Ctrl+C跟sigint是等价的所以当我们用seqint来停止这个进程的时候可以看到这个Resource Releases也被打印出来了然而如果我们使用的不是sigint而是sigterm的话可以看到finally里面的代码就没有被运行这个进程被直接的terminate掉了当然在python里面有专门的代码可以去处理这个sicterm在这里只是告诉大家如果你单纯的使用try finally的话面对sig turn你是无能为力的当然 CQ 就更不行了在我们之前的视频里面我们还提到过OS.下滑线AX这个函数在当时我们就告诉大家这个函数的机制非常底层他会直接调用一个system call所以在python里面大家应该尽量避免使用这个函数那在这里大家可以看到如果我使用的是OS.exit0finally 代码块也不会被运行当然在我们的实际工作中还有可能会遇到其他的可能性比如说SagFault如果你的代码sackfault了那当然finally是不会运行的如果大家还有其他感兴趣的情况,欢迎发到弹幕里或者评论区里。如果你有相关的经验与知识 也欢迎您在弹幕和评论区跟大家分享总结一下从python的层面看tryfinally依然是一个非常优秀的保证释放资源的手段它的形式简单一懂好写同时覆盖了大部分我们会遇到的可能性当然没有一个方法是完美的try finally也有它触及不到的情况对于这些情况如果我们心里有个数也会对我们的编程有所帮助好那这期视频就到这里希望对大家有所帮助如果喜欢的话记得点一下关注别忘了一键三连哦优优独播剧场——YoYo Television Series Exclusive