码农高天
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)