Python解释器退出机制分歧:sys.exit依赖异常易被截获

【python】Python的N种退出姿势,你都了解嘛?一期视频让你把每种方法都搞清楚! — 码农高天

码农高天

quit与sys.exit均通过抛出SystemExit异常终止进程,易被裸except语句静默拦截,而os._exit绕过解释器直通内核强制退出却会导致清理钩子失效。

AI-assisted summary of 【python】Python的N种退出姿势,你都了解嘛?一期视频让你把每种方法都搞清楚! — original source. Check the original for full context.

Source published:

Key ideas

  • quit 与 exit 均由 site 模块注入到内置作用域,本质都是抛出 SystemExit 异常;若启动时加入 -S 参数禁用 site 模块,调用二者将直接抛出 NameError,因此仅推荐在交互式解释器中使用。 (0:00)
  • sys.exit 同样基于触发 SystemExit 异常实现,但不受 -S 参数影响,是正式生产环境代码中推荐的标准退出方式。 (0:00)
  • SystemExit 继承自 BaseException 而非 Exception,写无类型限制的空 except 会意外捕获退出信号导致程序继续运行,因此规范代码应至少写为 except Exception。 (0:00)
  • os._exit 绕过 Python 异常机制直接发起内核系统调用强制杀掉进程,无法被 try...except 拦截且不会执行退出钩子函数,要求显式传入退出状态码,通常不建议在主进程中直接调用。 (5:00)

Detailed notes

## Background 该视频由技术博主“码农高天”于2023年10月22日发布在YouTube平台上。视频简介指出,内容旨在全面拆解退出Python的各种方式,深入探讨它们之间的区别、使用禁忌与潜在的技术陷阱。

## TL;DR 视频系统对比了Python中常见的几种退出机制及其底层原理:`quit()`和`exit()`依赖`site`模块注入,本质与`sys.exit()`一样都是抛出`SystemExit`异常,因此在生产环境中推荐使用`sys.exit()`,并需警惕裸写空`except:`导致退出被意外拦截。相比之下,`os._exit(status)`绕过了Python异常机制,直接发起内核系统调用强制终止进程,无法被任何`try...except`捕获,常用于特定子进程场景而非主进程。此外,视频还演示了通过`echo $?`检查进程退出状态码的技巧,以及在交互式终端中使用`Ctrl + D`(Unix)或`Ctrl + Z`加回车(Windows)发送EOF信号的极速退出方法。

## Atmosphere & style 整期视频呈现出资深程序员特有的干练与严谨风格,语速平稳而直击要害。高天结合CPython源码阅读与终端现场实操,将看似细枝末节的语法点剖析得透彻生动。讲解过程中既有对“千万不要写裸except”这种典型翻车隐患的严肃叮嘱,也有“无情退出”等生动风趣的技术吐槽,带给观众一种由表及里、豁然开朗的硬核解惑体验。

## Bulletin - [00:00](https://www.youtube.com/watch?v=m9nxQDfTYgY&t=0s) quit 与 exit 均由 site 模块注入到内置作用域,本质都是抛出 SystemExit 异常;若启动时加入 -S 参数禁用 site 模块,调用二者将直接抛出 NameError,因此仅推荐在交互式解释器中使用。 - [00:00](https://www.youtube.com/watch?v=m9nxQDfTYgY&t=0s) sys.exit 同样基于触发 SystemExit 异常实现,但不受 -S 参数影响,是正式生产环境代码中推荐的标准退出方式。 - [00:00](https://www.youtube.com/watch?v=m9nxQDfTYgY&t=0s) SystemExit 继承自 BaseException 而非 Exception,写无类型限制的空 except 会意外捕获退出信号导致程序继续运行,因此规范代码应至少写为 except Exception。 - [05:00](https://www.youtube.com/watch?v=m9nxQDfTYgY&t=300s) os._exit 绕过 Python 异常机制直接发起内核系统调用强制杀掉进程,无法被 try...except 拦截且不会执行退出钩子函数,要求显式传入退出状态码,通常不建议在主进程中直接调用。

## Key takeaways - [00:00](https://www.youtube.com/watch?v=m9nxQDfTYgY&t=0s) 日常退出 Python 程序的常见方式包括 quit、exit、sys.exit 以及 os._exit,但其底层运行机制与适用场景截然不同。 - [00:00](https://www.youtube.com/watch?v=m9nxQDfTYgY&t=0s) quit 和 exit 定义在 site 模块中,由解释器启动时自动注入 builtins 作用域的 quitter 实例,其底层通过关闭标准输入并抛出 SystemExit 退出。 - [00:00](https://www.youtube.com/watch?v=m9nxQDfTYgY&t=0s) 如果在启动 Python 时附加 -S 参数禁用 site 模块,quit 与 exit 将无法识别并报 NameError,因此正式生产代码中应统一使用 sys.exit。 - [00:00](https://www.youtube.com/watch?v=m9nxQDfTYgY&t=0s) sys.exit 在 CPython 源码的 sysmodule.c 中也是通过触发 SystemExit 异常来实现程序终止的。 - [00:00](https://www.youtube.com/watch?v=m9nxQDfTYgY&t=0s) 直接在代码中编写 raise SystemExit() 在功能上与调用 sys.exit() 完全等价,仅语义明确度有所差异。 - [00:00](https://www.youtube.com/watch?v=m9nxQDfTYgY&t=0s) 由于基于抛出异常,quit、exit 和 sys.exit 都可以被外层的 try...except SystemExit 拦截,从而导致进程未能如期终止。 - [00:00](https://www.youtube.com/watch?v=m9nxQDfTYgY&t=0s) 永远不要编写裸露的空 except:,因为它会连同 SystemExit 一起截获;应当捕获更具体的异常或使用 except Exception:。 - [05:00](https://www.youtube.com/watch?v=m9nxQDfTYgY&t=300s) os._exit 是操作系统内核级别的直通调用(如 POSIX 环境下的 exit 系统调用),不走 Python 异常链,完全无法被 try...except 捕获。 - [05:00](https://www.youtube.com/watch?v=m9nxQDfTYgY&t=300s) os._exit 必须显式传递一个整数 status 状态码,而 sys.exit、quit 和 exit 在缺省时会自动默认为状态码 0。 - [05:00](https://www.youtube.com/watch?v=m9nxQDfTYgY&t=300s) os._exit 能够保证立即退出程序,但坏处是会跳过所有已注册的退出钩子(hook)与清理逻辑,通常不推荐在主进程中使用。 - [05:00](https://www.youtube.com/watch?v=m9nxQDfTYgY&t=300s) 按照系统规范,退出状态码 0 代表程序正常运行完毕,非零值(如 1)代表异常退出,在 Shell 终端中可通过 echo $? 验证该状态码以供调度使用。 - [05:00](https://www.youtube.com/watch?v=m9nxQDfTYgY&t=300s) 交互式命令行支持 EOF 快捷退出:Unix 系统下为 Ctrl + D,Windows 环境下需输入 Ctrl + Z 并按回车。

## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=m9nxQDfTYgY&t=0s) Python 常见的退出姿势与 raise SystemExit 的机制

在日常编写 Python 程序时,当程序运行到某个特定阶段需要终止整个进程时,绝大多数开发者都会使用以下四种方式之一:`quit`、`exit`、`sys.exit` 以及 `os._exit`。但很多人并不清楚它们背后的运行机制和实际区别。

首先是 `quit` 和 `exit`,它们两个几乎是一模一样的,都定义在 Python 标准库的 `site` 模块(`site.py`)中。很多开发者会感到奇怪,自己平时并没有在代码顶部写过 `import site`,为什么能直接调用它们?原因在于 Python 正常启动时,解释器会自动导入 `site` 模块,并将 `quit` 与 `exit` 注入到全局内置作用域 `builtins` 命名空间中。翻看 CPython 源代码可以看到,`builtins.quit` 和 `builtins.exit` 都被赋值为一个名为 `quitter` 的实例。点进 `quitter` 的源码实现,其本质操作是在关闭标准输入(`standard in`)之后,抛出一个 `SystemExit` 异常。

再看 `sys.exit`,它的底层原理与 `quit`、`exit` 如出一辙,同样也是抛出 `SystemExit`。在 CPython 源码的 `sysmodule.c` 中(约第 820 行),`sys.exit` 的具体 C 语言实现核心就是触发 `SystemExit` 异常。既然这三者的本质相同,为什么还要做区分?

```mermaid flowchart TD accTitle: Python Exit Methods Comparison accDescr: 比较 quit、exit、sys.exit 与 os._exit 在实现原理上的分支流向

subgraph UserCalls ["调用方式"] Q["quit() / exit()"] S["sys.exit()"] R["raise SystemExit()"] O["os._exit(status)"] end

subgraph SiteModule ["site 模块注入"] Q -->|"依赖 site 模块 (可用 -S 禁用)"| SExit["触发 SystemExit 异常"] end

subgraph SysLevel ["sys / 直接抛出"] S --> SExit R --> SExit end

subgraph KernelLevel ["系统调用"] O -->|"直接系统调用 (如 exit_group/ExitProcess)"| SysCall["内核直接终止进程 (无法被 try/except 捕获)"] end

SExit -->|"可被 Python 代码拦截"| Catch{"try... except 捕获"} Catch -->|"空 except / except SystemExit"| Suppress["进程未退出,继续执行后续代码"] Catch -->|"未捕获"| NormalExit["正常退出解释器"] ```

关键差异在于对运行环境的依赖。`quit` 和 `exit` 虽然方便,但强烈依赖解释器启动时自动加载的 `site` 模块。如果用户在启动 Python 时附加了 `-S` 参数(即 `python -S`,表示不导入 `site` 模块),这两个名称根本就不会被注入内置作用域中,代码会直接抛出 `NameError`。码农高天在此强调:“这两种写法只推荐大家在 Interactive Interpreter 中用,就是当你打 Python 然后一行一行那么输命令的时候你可以用,因为他们俩打起来比较快;在所有的比较正式的代码里大家都要用 `sys.exit`。”

因为前三者都是通过 `raise SystemExit` 来实现的,这意味着退出行为完全可以被 `try...except` 语句捕获: - 如果在 `quit()` 或 `sys.exit()` 外层包裹了 `try:... except SystemExit:`,并在异常分支中打印日志,代码并不会真正终止,后续的打印语句依然会照常执行。 - 这种情况甚至会因为糟糕的编码习惯而引发隐蔽的 Bug。有些开发者喜欢写毫无类型限制的空捕获 `except:`。若在 `try` 块里调用退出函数,空 `except:` 也会将 `SystemExit` 截获下来,导致进程继续往后跑。 - 现代 IDE(如 PyCharm 或安装了相关插件的 VS Code)都会警告不要写裸露的 `except:`,最起码应写成 `except Exception:`。由于 `SystemExit` 继承自 `BaseException` 而非 `Exception` 的派生类,使用 `except Exception:` 就不会误拦截退出信号。码农高天严正告诫:“大家一定要记住啊,永远不要写一个空的 `except:`,会出很多奇怪的问题。” - 既然三者的本质都是抛出异常,那么在代码中直接写 `raise SystemExit()` 在功能上是完全等价的,只是在语义层面上,显式调用退出函数往往更明确。

[05:00](https://www.youtube.com/watch?v=m9nxQDfTYgY&t=300s) 底层系统调用 os._exit、状态码与终端 EOF 退出姿势

第四种方法 `os._exit` 则与其他三种有着本质区别。它根本不走 Python 异常那一套,而是直接发起操作系统的内核调用(system call)。在不同操作系统底层(如 POSIX 环境下封装相关的 exit 系统调用),其背后机制虽因内核差异而略有不同,但无论在哪个系统上,“他都会不管三七二十一直接退出你的程序”。

在直接运行 `os._exit()` 时,如果未传参,Python 会直接抛出错误提示缺少必需参数 `status`。这是因为在操作系统层面,每个进程结束时都必须向操作系统上报自身的退出状态(status code): - 约定俗成地,状态码为 `0` 代表一切正常。 - 所有的非零整数代表异常退出,不同数值可以指代不同的错误原因(常见的非零状态码通常用 `1` 表示出现错误)。 - 前面三种基于 Python 机制的函数(`quit`、`exit`、`sys.exit`)在未显式传递参数时,默认都会替用户填入 `0`,而 `os._exit` 是直通内核系统调用的接口,因此必须强制显式传递一个整数状态码(例如 `os._exit(0)`)。

`os._exit` 最显著的特点是冷酷而直接。即便在外层套上 `try... except SystemExit:` 甚至裸写空 `except:`,它也“依然无情的退出了”,后面的异常分支代码根本没有机会执行。码农高天评价道:“跟 raise 一个 SystemExit 比,它也是有好有坏:好处是你可以保证它当下立刻马上退出,坏处就是你可能本来写了一些退出时候运行的一些 hook、一些代码,那些代码就都运行不了了;那一般来说,在你的主进程是不太推荐使用这种退出方式。”

关于进程结束的状态码验证,在 Linux 与 macOS 的终端中,可以在 Python 脚本执行结束后立即运行 `echo $?` 查看上一条命令的退出状态: - 传递 `0`,`echo $?` 便输出 `0`。 - 如果将代码改为 `os._exit(1)` 或 `sys.exit(1)`,终端打印出的状态码就是 `1`。在多进程编程或 Shell 调度中,父进程常常依赖这个状态码来研判子进程究竟是成功跑完还是中途崩溃。

除上述函数式退出方法外,在 Python 的交互式命令行(Interactive Shell)中,还存在更加高效快捷的 EOF(End of File)退出姿势: - **Unix 环境(Linux / macOS)**:直接按下快捷键 `Ctrl + D`。此时 Python 在解析标准输入(parse standard in)时捕获到 EOF,便会自动退出交互界面。 - **Windows 环境**:需要先按下 `Ctrl + Z`,紧接着敲击回车键(Enter),即可退出。

这两种方式的底层都是解释器专门针对 Interactive Shell 输入流所做的 parsing 逻辑判断,代码层级较深且复杂,但在命令行日常调试中,其效率往往比手动敲出 `exit()` 加回车要快得多。

## Notable quotes > [00:00](https://www.youtube.com/watch?v=m9nxQDfTYgY&t=0s) 大家一定要记住啊,永远不要写一个空的 except:,会出很多奇怪的问题。 — 码农高天

## Mentioned Python, CPython, 码农高天, Linux, macOS, Windows, PyCharm, VS Code

## Video description 在你用各种方式退出python的时候,有没有想过它们有什么不同呢?又有什么禁忌呢?哪里会有坑呢?这期视频带你全面了解,退出Python的N种姿势!

[Source](https://www.youtube.com/watch?v=m9nxQDfTYgY)

中文简报

Python解释器退出机制对比:sys.exit依赖异常易被拦截 quit与sys.exit均通过抛出SystemExit异常终止进程,易被裸except语句静默拦截,而os._exit绕过解释器直通内核强制退出却会导致清理钩子失效。 视频系统对比了Python中常见的几种退出机制及其底层原理:`quit()`和`exit()`依赖`site`模块注入,本质与`sys.exit()`一样都是抛出`SystemExit`异常,因此在生产环境中推荐使用`sys.exit()`,并需警惕裸写空`except:`导致退出被意外拦截。相比之下,`os._exit(status)`绕过了Python异常机制,直接发起内核系统调用强制终止进程,无法被任何`try...except`捕获,常用于特定子进程场景而非主进程。此外,视频还演示了通过`echo $?`检查进程退出状态码的技巧,以及在交互式终端中使用`Ctrl + D`(Unix)或`Ctrl + Z`加回车(Windows)发送EOF信号的极速退出方法。 quit与exit均由site模块注入到内置作用域,本质都是抛出SystemExit异常;若启动时加入-S参数禁用site模块,调用二者将直接抛出NameError,因此仅推荐在交互式解释器中使用。 (0:00) sys.exit同样基于触发SystemExit异常实现,但不受-S参数影响,是正式生产环境代码中推荐的标准退出方式。 (0:00) SystemExit继承自BaseException而非Exception,写无类型限制的空except会意外捕获退出信号导致程序继续运行,因此规范代码应至少写为except Exception。 (0:00) os._exit绕过Python异常机制直接发起内核系统调用强制杀死进程,无法被try...except拦截且不会执行退出钩子函数,要求显式传入退出状态码,通常不建议在主进程中直接调用。 (5:00)

Reading summary

码农高天系统解析了 Python 中常见的程序退出方式,指出其本质主要分为基于异常抛出(SystemExit)与直接发起操作系统内核调用两大路径,并建议根据运行场景严格区分使用。 解释器层面的 SystemExit 机制及其捕获隐患 (0:00) 在 Python 中,quit、exit 与 sys.exit 三者的底层原理完全一致,最终都是通过抛出 SystemExit 异常来请求终止进程;直接编写 raise SystemExit() 在功能上也是等价的。quit 和 exit 定义在 site.py 中,由解释器启动时注入 builtins 命名空间的 quitter 实例实现,本质是关闭标准输入后触发异常;sys.exit 的 C 源码(sysmodule.c)核心同样是抛出该异常。因为是异常机制,这些退出调用均可被 try...except 捕获。 由于 SystemExit 继承自 BaseException 而非 Exception,代码若编写规范的 except Exception: 不会误拦截退出信号。但若使用无类型的空 except:,或者显式指定捕获 except SystemExit:,退出流程就会被截断,导致进程继续向下执行。高天告诫开发者切勿编写裸露的空 except:,以免引发程序无法正常退出的隐蔽错误。 开发场景规范:交互终端与正式代码的区别 (0:00) 尽管 quit、exit 与 sys.exit 底层机制相同,但由于 quit 和 exit 高度依赖 Python 启动时自动加载的 site 模块,在正式生产环境中存在潜在隐患。如果在启动 Python 时附加了 -S 参数(python -S 表示禁用 site 模块),这两个名称将不会被注入全局作用域,调用时会直接报错 NameError。 因此,高天强调 quit 和 exit 仅推荐在命令行交互式解释器(Interactive Interpreter)中作为快捷输入使用。在所有较为正式的工程代码与脚本中,必须使用标准库的 sys.exit 来保证代码在不同运行环境下的稳定性。 底层系统调用 os._exit 与退出状态码 (5:00) 第四种方式 os._exit 脱离了 Python 异常系统,直接发起操作系统的内核级系统调用(如 POSIX 环境下的相关 exit 调用),会强行终止整个进程。即便外部包裹了 try...except SystemExit 或空 except:,也完全无法拦截其退出行为。由于跳过了常规清理流程,注册的退出 hook 与相关后续代码均不会运行,因此高天指出主进程中一般不推荐使用 os._exit,其主要价值在于必须即刻、无条件终止的特殊场景。 与前三者默认填充状态码 0 不同,os._exit 必须显式传递一个整数类型的 status 参数,否则会报错。根据系统约定,状态码 0 代表程序正常退出,非零值(如 1)代表异常退出。在 Linux 与 macOS 终端中,调用后可通过 echo $? 验证该状态码,这一机制在 Shell 调度与多进程编程中常被父进程用于判断子进程的执行结果。 终端交互环境下的 EOF 快捷退出姿势 (5:00) 除上述函数调用外,在交互式命令行中还可以利用 EOF(End of File)机制快速离开环境。在 Unix 环境(Linux 与 macOS)下,可直接按下快捷键 Ctrl + D;在 Windows 环境下,则需输入快捷键 Ctrl + Z 并紧接着敲击回车键。 这两种方式的底层依赖解释器针对标准输入流所设定的复杂解析逻辑,相比手动输入 exit() 或 quit() 更加高效,是日常交互调试中推荐的退出技巧。 总体而言,正式工程应优先选用 sys.exit 并杜绝空 except 捕获,强行即时终止且不执行退出 hook 时可选用 os._exit,而 quit、exit 及 EOF 快捷键则主要服务于交互式调试场景。

精炼阅读摘要

码农高天解析了 Python 中四种常见退出方式及终端快捷退出的底层机制,指出 quit、exit 和 sys.exit 本质上均基于抛出 SystemExit 异常,而 os._exit 则直接调用操作系统内核强制终止进程。 基于 SystemExit 的上层退出机制与环境依赖 (0:00) 在日常 Python 编程中,quit、exit 与 sys.exit 表面形态不同,但在 CPython 底层实现中三者殊途同归,本质均是通过抛出 SystemExit 异常来终止程序。其中,quit 和 exit 均定义在标准库的 site.py 中,由 Python 解释器在常规启动时自动加载并注入到 builtins 全局命名空间。CPython 源码中将它们实现为一个名为 quitter 的对象实例,在关闭标准输入后触发 SystemExit。而 sys.exit 的 C 语言实现位于 sysmodule.c 约第 820 行,同样以触发 SystemExit 为核心机制。 尽管底层效果相似,但 quit 和 exit 高度依赖解释器默认加载的 site 模块。若在启动 Python 时附加了 -S 参数以禁用 site 模块,全局作用域中便不会注入这两个函数,直接调用会触发 NameError。因此,码农高天建议开发者仅在交互式解释器(Interactive Interpreter)中为追求键入速度而使用 quit 或 exit,在所有正式的项目代码与生产环境中,应统一采用 sys.exit 或直接显式调用 raise SystemExit()。 异常捕获与裸写 except 的逻辑缺陷 (0:00) 由于 quit、exit 与 sys.exit 的退出逻辑完全建立在抛出 SystemExit 异常之上,这意味着它们的退出流程完全可以被常规的 try...except 语句拦截。若在这些退出调用外层包裹了捕获 SystemExit 的代码块,并在分支中执行其他操作,进程将不会退出,后续逻辑仍会继续执行。直接使用 raise SystemExit() 在功能上与调用这些函数完全等价,只是语义形式存在差别。 更为隐蔽的缺陷发生在开发者编写无类型限制的裸露 except: 代码时。由于 SystemExit 继承自 BaseException 而非普通的 Exception,规范书写的 except Exception: 并不会误捕获退出信号;但没有任何类型的空 except: 则会将包括 SystemExit 在内的所有异常一并截获,导致退出指令失效并使程序意外继续往下运行。现代 IDE 通常会对此类写法发出警告,码农高天也告诫开发者切勿在代码中裸写空 except,以规避异常逻辑被屏蔽带来的故障。 os._exit 的内核级系统调用与状态码 (5:00) 第四种方法 os._exit 与前述三种退出方式存在本质鸿沟。它完全绕过 Python 语言层面的异常抛出与处理机制,直接向底层操作系统发起内核级系统调用(如 POSIX 环境下的相关 exit 系统调用)。由于该调用属于硬性终止,外部包裹的任何 try...except 甚至空 except 都无法对其进行拦截,程序会立即中断,且开发者编写的退出勾子(hook)或清理代码均不会得到执行。码农高天指出,由于缺乏安全清理缓冲,通常不建议在主进程中随意使用 os._exit。 此外,每个进程在结束时均需向操作系统上报状态码(status code)。通用约定下,状态码 0 代表程序正常执行完毕,非零整数(常见如 1)代表发生异常退出。基于 Python 异常机制的退出函数在未传参时均默认填入状态码 0,但 os._exit 作为一个直通内核的接口,强制要求调用者显式传入一个整数状态码参数,否则会抛出缺少参数的错误。在 Unix 终端环境中,开发者可在脚本执行后运行 echo $? 命令,以此验证子进程实际反馈给操作系统的退出状态。 终端交互环境下的 EOF 退出方案 (5:00) 除调用代码内置的函数外,在交互式命令行环境(Interactive Shell)中,Python 还提供了基于文件结束符(EOF, End of File)的快捷退出方案。该机制属于解释器针对交互式输入流专门构建的解析(parsing)逻辑。 在 Linux 与 macOS 等 Unix 环境下,用户只需在终端中按下 Ctrl + D 组合键,解释器解析标准输入捕获到 EOF 便会自动退出;在 Windows 环境下,用户则需按下 Ctrl + Z 并紧接着敲击回车键触发退出。此类方式省去了在命令行中手动键入完整函数调用并执行回车的过程,是日常交互调试中效率更高的退出操作。 选择退出方式的核心权衡在于是否需要执行上层异常拦截与清理逻辑:正式代码应优先使用受控抛出 SystemExit 的 sys.exit;os._exit 虽能确保即时杀死进程,但会跳过所有退出勾子,主进程中须审慎调用。

Detailed notes — zh

## Background 该视频由 YouTube 频道“码农高天”于 2023 年 10 月 22 日发布。视频旨在系统解析 Python 中各种退出程序的姿势,详细拆解不同退出方式的底层原理、潜在隐患与使用禁忌。 ## TL;DR 视频深入对比了 Python 中常见的退出方法,包括 quit、exit、sys.exit 以及 os._exit。主讲人指出 quit 和 exit 依赖 site 模块且仅推荐在交互式环境中使用,而 sys.exit 是正式代码的标准选择,但两者本质上都是抛出 SystemExit 异常,极易被危险的“裸 except”误捕获。相比之下,os._exit 则是直接发起操作系统内核调用无条件终止进程,会跳过所有清理钩子;最后还补充了通过快捷键触发 EOF 的终端退出机制与进程状态码规范。 ## Atmosphere & style 视频整体风格严谨专业而又深入浅出,带有典型的硬核源码级教学质感。讲者通过翻阅 CPython 底层源码并现场编写反面案例,条理清晰地推导设计原由,语调沉稳且不乏对常见反模式的严肃警示,让学习者能直观感受到从表层语法到系统底层的贯通感。 ## Bulletin - [00:00](https://www.youtube.com/watch?v=m9nxQDfTYgY&t=0s) 交互式命令 `quit()` 与 `exit()` 均依赖 `site` 模块注入,若使用 `python -S` 启动解释器将导致其不可用,因此生产工程代码必须统一使用 `sys.exit()`。 - [02:35](https://www.youtube.com/watch?v=m9nxQDfTYgY&t=155s) `sys.exit()` 与 `quit()` 底层本质都是抛出 `SystemExit` 异常,书写无类型的裸 `except:` 会误捕获退出信号阻断程序终止,应遵循规范显式声明 `except Exception:`。 - [04:25](https://www.youtube.com/watch?v=m9nxQDfTYgY&t=265s) `os._exit(status)` 直接发起操作系统内核系统调用立即终止进程,强制要求提供状态码参数,且会完全跳过 Python 层的清理钩子与异常捕获,一般不建议在主进程中使用。 ## Key takeaways - [00:00](https://www.youtube.com/watch?v=m9nxQDfTYgY&t=0s) 内置命令 `quit` 与 `exit` 本质是 `site` 模块注入到全局 `builtins` 中的同一个 `quitter` 对象,二者除了命名外毫无区别。 - [00:00](https://www.youtube.com/watch?v=m9nxQDfTYgY&t=0s) 使用 `python -S` 参数启动解释器将阻止载入 `site` 模块,导致内置的 `quit` 和 `exit` 均不可用并报错。 - [00:00](https://www.youtube.com/watch?v=m9nxQDfTYgY&t=0s) `sys.exit` 是定义在 CPython 源码 `sysmodule.c` 中的 C 接口,在所有正式代码与生产环境中都应作为标准退出函数使用。 - [00:00](https://www.youtube.com/watch?v=m9nxQDfTYgY&t=0s) `quit`、`exit` 和 `sys.exit` 的底层实现都是主动触发并抛出 `SystemExit` 异常,其效果与在代码中手动书写 `raise SystemExit` 完全一致。 - [02:35](https://www.youtube.com/watch?v=m9nxQDfTYgY&t=155s) 由于 `SystemExit` 属于可捕获异常,外层的 `try...except` 语句可以拦截退出行为,使程序在触发退出后仍继续执行后续逻辑。 - [02:35](https://www.youtube.com/watch?v=m9nxQDfTYgY&t=155s) `SystemExit` 继承自 `BaseException` 而非 `Exception`,因此不带任何异常类型的裸 `except:` 会将其误捕获,破坏进程正常退出控制流。 - [04:25](https://www.youtube.com/watch?v=m9nxQDfTYgY&t=265s) `os._exit` 是面向操作系统的底层内核调用(System Call),具有跨平台差异(如 Linux 下调用 `posix._exit`),会直接强行杀死进程。 - [04:25](https://www.youtube.com/watch?v=m9nxQDfTYgY&t=265s) `os._exit` 强制要求显式传入退出状态码(如 `os._exit(0)`),无法像 `sys.exit()` 那样在不传参时自动默认补齐状态码 `0`。 - [04:25](https://www.youtube.com/watch?v=m9nxQDfTYgY&t=265s) 操作系统的退出状态码规范以 `0` 代表正常退出,非零值(如 `1`)代表各类异常退出,在 Unix/Linux 终端中可通过 `echo $?` 查看。 - [04:25](https://www.youtube.com/watch?v=m9nxQDfTYgY&t=265s) `os._exit` 会跳过程序预设的所有退出清理逻辑和钩子代码(Hooks),无法被 `try...except` 拦截,因此通常仅限子进程或强制熔断场景使用。 - [04:25](https://www.youtube.com/watch?v=m9nxQDfTYgY&t=265s) 在交互式命令行终端中,可通过输入流解析触发 EOF 快速退出:Unix/Linux/macOS 环境使用 `Ctrl+D`,Windows 环境使用 `Ctrl+Z` 加回车。 ## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=m9nxQDfTYgY&t=0s) Python常见的退出姿势与 `site` 模块机制 在日常编写 Python 程序时,当程序运行到某个特定阶段需要停止并退出整个进程,大家最常用的通常是四种退出方式:`quit`、`exit`、`sys.exit` 以及 `os._exit`。其中 `quit` 和 `exit` 几乎一模一样,它们均属于 Python 内部一个名为 `site` 的模块。平时之所以能够直接调用而无需手动 `import site`,是因为 Python 在正常启动运行环境时,会自动引入 `site` 模块,并将 `quit` 和 `exit` 注入到全局内置作用域 `builtins` 当中。查看底层源代码可以发现,`builtins.quit` 和 `builtins.exit` 都被赋值为了同一个 `quitter` 对象,两者除了名字不同外毫无差别。进入 `quitter` 的具体实现便能看到,它的本质逻辑是在关闭标准输入(standard in)之后,直接主动 `raise` 了一个 `SystemExit` 异常。 ```mermaid flowchart TD accTitle: Python中常见退出函数的底层机制 accDescr: 展示quit、exit、sys.exit与os._exit在底层实现上的分化路径 subgraph UserCalls["调用入口"] Q["quit() / exit()"] S["sys.exit()"] O["os._exit(status)"] end subgraph SiteModule["site 模块 (可被 -S 禁用)"] Quitter["quitter 对象<br/>(关闭 stdin 并抛出异常)"] end subgraph PythonException["Python 异常机制"] SE["raise SystemExit"] CatchBlock["可被 try...except 捕获"] end subgraph KernelCall["操作系统内核"] Syscall["系统调用 exit_group / ExitProcess"] HardExit["无视清理代码,直接终止进程"] end Q --> Quitter --> SE S -->|"C API 层面实现"| SE SE --> CatchBlock O --> Syscall --> HardExit ``` 与此同时,`sys.exit` 在底层机制上也和前两者一致,其本质同样是抛出 `SystemExit`。查阅 CPython 源码中的 `sysmodule.c`,在第 820 行附近的 `sys.exit` 实现处,其底层对应操作就是抛出了一个 `SystemExit`。然而,`quit`/`exit` 与 `sys.exit` 之间存在着一个关键差异:`site` 模块并非在所有场景下都会被自动加载。如果使用 `python -S` 参数启动解释器,Python 将不会自动导入 `site` 模块,此时作用域里根本不存在 `quit` 和 `exit` 这两个名字,直接调用就会报错。因此,码农高天强调道:“在所有的比较正式的代码里大家都要用sys.exit”,而 `quit` 和 `exit` 仅仅推荐在交互式解释器(Interactive Interpreter)中作为快捷简写来敲命令使用。 - `quit` 与 `exit`:由 `site` 模块注入到 `builtins`,本质是 `quitter` 对象,关闭 stdin 并抛出 `SystemExit`。 - `python -S`:启动参数,阻止载入 `site` 模块,导致 `quit` 与 `exit` 不可用。 - `sys.exit`:定义在 `sysmodule.c`(约 820 行),通过 C 接口抛出 `SystemExit`,正式工程代码中的标准退出方式。 ### [02:35](https://www.youtube.com/watch?v=m9nxQDfTYgY&t=155s) `SystemExit` 的异常捕获与裸 `except` 的陷阱 由于 `quit`、`exit` 和 `sys.exit` 的底层原理都是抛出 `SystemExit` 异常,这意味着该退出行为完全可以被外层的 `try...except` 语句拦截捕获。例如在 `quit()` 后放置一个 `except SystemExit:` 块,并在里面打印提示信息,程序在遇到退出语句时并不会真正退出,而是进入异常处理分支并继续执行后续的代码逻辑。这种设计本身各有利弊:好处在于 Python 赋予了开发者极其灵活的处理退出的能力;坏处则是很多时候开发者误以为程序已经终止,但它实际上却被拦截并继续存活。 更加危险的情况发生在使用裸 `except:`(没有任何异常类型的空捕获)的代码风格中。码农高天演示了写一个空的 `except:` 捕获代码块,运行后会发现它依然能够将包含退出的 `SystemExit` 捕获掉,导致下方的打印逻辑继续被执行。现代 IDE(如 PyCharm 或安装了插件的 VS Code)通常会对裸 `except:` 发出警告,提示至少要写成 `except Exception:`。因为 `SystemExit` 继承自 `BaseException` 而非 `Exception` 的派生类(Derived Class),写成标准的 `except Exception:` 就不会误拦截退出异常,从而使程序能够顺利终止。针对这一常见语法漏洞,码农高天严肃警示道:“所以大家一定要记住啊永远不要写一个空的except冒号会出很多奇怪的问题”。此外,既然前三种方法的本质均等同于 `raise SystemExit`,那么在代码中直接手动写 `raise SystemExit` 也是完全等价的实现,只是在语义层面上不如直接调用函数那样清晰明确。 - `SystemExit` 继承关系:继承自 `BaseException`,而非普通业务异常基类 `Exception`。 - 裸 `except:` 陷阱:会捕获包括 `SystemExit`、`KeyboardInterrupt` 等在内的所有底层异常,阻断进程正常退出的控制流。 - 等价调用:在代码中直接执行 `raise SystemExit` 与调用 `sys.exit()` 的退出效果完全一致。 ### [04:25](https://www.youtube.com/watch?v=m9nxQDfTYgY&t=265s) `os._exit` 的内核级终止与退出状态码机制 与前三种依赖 Python 异常机制的退出方式不同,`os._exit` 是直接执行操作系统内核调用(System Call)。由于底层直接面向系统调用,它在不同操作系统(如 Linux POSIX 系统与 Windows)下的底层实现各不相同,但共同特性是不管三七二十一直接立刻终止进程。在 `os` 模块源码中,如果检测到 POSIX 环境,会直接导入底层的 `posix._exit` 接口。如果直接无参调用 `os._exit()`,程序会抛出异常,提示需要一个状态参数(`status argument`)。这是因为操作系统要求每一个进程在结束生命周期时,都必须向操作系统内核汇报自身的结束情况。通常规范是以状态码 `0` 表示一切正常退出,所有非零数值代表异常退出,不同数值对应不同的异常类型。虽然 `sys.exit()` 等 Python 层函数在不传参时会默认填充状态码 `0`,但直接作为系统调用接口的 `os._exit` 则强制要求必须显式传入状态参数(例如 `os._exit(0)`)。 `os._exit` 具备极强的强制性,即便在外部包裹 `try...except BaseException`,后续代码也绝不会执行,进程会被无情杀掉。码农高天指出:“坏处就是你可能本来写了一些退出时候运行的一些hook一些代码那些代码就都运行不了了那一般来说在你的主进程是不太推荐使用这种推出方式”。在 Linux 或 macOS 终端中,用户可以通过执行 `echo $?` 来获取上一个已结束进程的退出状态码。无论是 `os._exit(1)` 还是 `sys.exit(1)`,传入的参数都会忠实地反映在终端状态码中,这常被父进程用来判断拉起的子进程是否健康退出。此外,在交互式命令行终端(Interactive Shell)中,还存在基于 EOF(End of File)的文件结束退出方式:Unix/Linux/macOS 环境下可以直接按下 `Ctrl+D`,而 Windows 环境下则需要输入 `Ctrl+Z` 再按回车键。这类机制属于 Python 在针对交互式 Shell 层面做输入流解析(parse standard in)时的专门判断,虽然代码调用链路深且复杂,但使用起来比敲击完整命令更为快捷。 | 退出方式 | 所属模块 / 触发机制 | 底层原理 | 是否触发清理代码 (Hooks/Finally) | 常用场景 | |:--- |:--- |:--- |:--- |:--- | | `quit()` / `exit()` | `site` 模块(注入到 `builtins`) | 抛出 `SystemExit` 异常 | 是(可被异常捕获并正常清理) | 仅交互式 Shell 中敲命令 | | `sys.exit([code])` | `sys` 模块(C API 层面) | 抛出 `SystemExit` 异常 | 是(允许执行退出清理逻辑) | 正式生产代码、主进程标准退出 | | `os._exit(status)` | `os` 模块(系统级原生封装) | 内核级 System Call | 否(立即终止,跳过清理钩子) | 子进程或需强制熔断退出的场景 | | `Ctrl+D` (Unix) / `Ctrl+Z+Enter` (Win) | 交互式终端输入流解析 | 触发 EOF (End of File) 判断 | 是 | 终端交互式命令行快速退出 | - 状态码规范:`0` 代表正常退出,非 `0`(常见为 `1`)代表异常退出。 - 查看退出码:在 Linux/macOS 终端中通过 `echo $?` 命令查询上一条进程返回的状态码。 - `os._exit` 的优缺点:优点是立刻、马上无条件退出,缺点是会跳过 Python 层面的清理回调和钩子(hook)函数,不建议在主进程中随意使用。 ## Notable quotes > [00:00](https://www.youtube.com/watch?v=m9nxQDfTYgY&t=0s) 在所有的比较正式的代码里大家都要用sys.exit — 码农高天 > [02:35](https://www.youtube.com/watch?v=m9nxQDfTYgY&t=155s) 永远不要写一个空的except冒号会出很多奇怪的问题 — 码农高天 ## Mentioned Python, CPython, Linux, macOS, Windows, POSIX, PyCharm, VS Code, 码农高天 ## Video description 在你用各种方式退出python的时候,有没有想过它们有什么不同呢?又有什么禁忌呢?哪里会有坑呢?这期视频带你全面了解,退出Python的N种姿势! [Source](https://www.youtube.com/watch?v=m9nxQDfTYgY)

Source quotes

[0:00] 码农高天: 在所有的比较正式的代码里大家都要用sys.exit [2:35] 码农高天: 永远不要写一个空的except冒号会出很多奇怪的问题

Source quotes

[0:00] 码农高天: 大家一定要记住啊,永远不要写一个空的 except:,会出很多奇怪的问题。

Original transcript

[0:00] 这期视频我们来讲一下N种从Python退出的姿势我相信大家一定会经常用到退出这个功能就是程序运行到某个阶段然后我就不再继续运行了我想把整个进程退出来我相信大部分人用的退出方式应该就是左边这四种分别是quitexit,sys.exit,跟os.exit.那你知道这四种方法之间他们有什么不同吗那我们先来说前两个 quit 跟 exit几乎是一模一样的它们两个都属于一个叫Site的module那你说为什么我用的时候从来没有import过这个site module呢因为在你正常运行python的时候它是会自动帮你把这个site module import进来并且把quit跟exit这两个东西放到builtins里这样你在你程序的任何一个地方运行这两个函数它都能退出来我们可以看这就是它的原代码他就是把这个builtins点quit跟exit都复制成了这个quitter他们俩除了名字之外 剩下几乎是一模一样的我们可以点进quitter看一下啊它本质就是把standard in close之后然后raise了一个system exit那我们再说这个sys的exitSys.exit跟前两个quit跟exit原理也是一样的它同样相当于raise的System.exit我们可以看这是SysModule.c里面Sys.exit的implementation在第820行这里他就相当于是raise了一个system exit好那前三种方式 quit exit包括sys exit他们的原理是一样的他们有什么不同呢这个quit跟exit我们说了几乎是一模一样的但是他们俩有个问题 我们之前不是说了 他们是属于set这个module的 但python并不总是在运行的时候会自动import这个module我们看这种运行方式当我们用python-s的时候它就会不import这个site module这个时候 quit跟 exit这两个名字就不存在了因此这两种写法只推荐大家在Interactive Interpreter中用就是当你打Python然后一行一行那么书命令的时候你可以用因为他们俩打起来比较快在所有的比较正式的代码里大家都要用sys.exit好 那我们说前三者的原理都是Race的一个System Exit Exception也就是说前三者都会被这个try,except给catch到我们看左边的代码,我们quit之后呢,catch了一个system exit然后在这个exception里我们就print一个东西我们运行一下折代码大家可以看到quit并没有起到它本身应该起的作用就是退出程序我们的第12行的print依然打印出来了那么这种行为本身可以说是有好有坏好的地方是python允许你更灵活的处理退出这件事情那坏的是呢当你以为你退出了的时候其实这个程序他可能没退出尤其有一些人还喜欢这么些程序像这样他写一个空的except那如果你真的写程序的话 我们运行一下你会发现它依然没有正常的退出他还是把这个业给打印出来了当然了如果你用PyCharm或者你用一些VS code的插件的话它这块会提醒你不能这么写他告诉你要写成这个形式你要做一个accept exception 这是最起码的那如果写成这个形式的话 我们跑一下可以看到第12行就没有打印了因为SystemEdit并不是Exception的一个DerivedClass所以大家一定要记住啊永远不要写一个空的except冒号会出很多奇怪的问题好 那我们已经知道前面三种方法的本质都是Race的一个System Exit 对不对那这个时候呢 我们其实就能想到一种并不是那么常用的 但是跟他们三个等价的方法 就是直接raise这个exception大家看第五行 我可以直接把Exception raise出来 它的效果是一样的我运行了一下 结果跟之前一样所以你当然也可以写这种等价形式只是语意上说它并没有另外三个那么明确好 说到这你肯定已经能感受到最后一个OS.下滑线exit跟其他三个不一样了否则我们就放到一起说了对吧那这个OS.下销线exit呢它是直接做了一个内核调用做了一个system call它在不同的操作系统下背后原理是不同的毕竟system call不一样但是在所有的系统下他都会不管3721直接退出你的程序我们可以简单看一下就是在这个OS module里面如果posit在的话它会直接在posit里面引破下来exit然后这个下游线exit就是一个做了一个system call的函数好我们看如果直接运行这套代码的话会发生什么我们看他报错了对不对 [5:00] 他说这个exit需要一个status argument这是什么意思呢就是操作系统需要每一个进程在结束的时候向操作系统报告一下自己结束的情况是否是正常的结束了这个就是退出时候的状态码一般来说用0代表一切正常用所有的非灵术代表是异常退出然后用不同的值代表不同的异常那有人可能问为什么之前那些函数没有状态码没事我们说过那些函数是python的一个机制对吧当你不给他状态码的时候 他就默认是零正常退出当然实际上你也可以给那些寒入状态码那对于OS.下游线exit来说由于它直接就是一个system call的interface所以你必须要给它一个状态码那一般来说如果我们认为这是一个正常的退出的话我们就给一个零这个程序长成这个样子我们再来运行一下可以看到它就正常退出了那在之前我们用那三个函数的时候我们说我们可以try accept然后拿到这个System Exit通过处理这个exception让他不退出我们试一下在OS下方线 exit行不行我们把程序写成这个样子然后再运行一下可以看到它依然无情的退出了第8行并没有被打印出来这个是OS.下游线exit的特点它退出的时候是不管任何其他乱七八糟的东西的它就直接退掉了那同样的跟RAID的一个system exit比它也是有好有坏好处是你可以保证它当下立刻马上退出坏处就是你可能本来写了一些退出时候运行的一些hook一些代码那些代码就都运行不了了那一般来说在你的主进程是不太推荐使用这种推出方式好 那我们既然刚才已经提到了进程结束时候的状态码 我们就看一下这个状态码那在Linux包括Mac里我们可以通过echo$问号来拿到上一个进程结束时候的状态我们可以看到它退出时候状态码是0我们把程序改成os.下游线 exit1 然后运行一下程序我们再尝试拿一下这个状态码可以看到这个状态码就是1了那相似的不管是quick exit 还是我更推荐大家使用的sys.exit我们都可以传进去状态码我们把这个程序运行一下然后拿下这个进程结束时候的状态码可以看到状态码是0我们把sys的access里面变成1可以看到他结束的状态码就是1了那么这个状态码经常被拿来判断我自己新建的进程有没有正确的运行退出所以大家不论是在用sys.exit或者是用os.exit的时候都应该考虑清楚我这个状态码应该给什么是正常退出的话就要给0是非正常退出的话一般就给一个非0的数那么很常见的是用1来表示有错误当然除了我们上面说到的很多种退出方法之外在Interactive Shell里面还有一些退出方式我们可以把它们统称为EOF就是end of file的推出方式那这些退出方式呢可能比你打一个exit括号回车还要更快一点在unix下就是linux和mac你都可以通过按ctrl+d的方式退出那python实际上是在parse standard in的时候去判断EUF那如果是在Windows上呢 你可以先按Ctrl+Z 然后再按回车来退出python的interactive shell那这两者的原理呢实际上都是python在专门针对interactive shell的这个部分做的parsing的一些判断它代码层级比较深也比较复杂 我们这一次就不讲了好 那以上就是我们这期视频的全部内容关于退出的各种姿势你都学会了吗如果喜欢这期视频的话 别忘了点一下关注 记得一键三连哦字幕志愿者 杨茜茜