您现在的位置是:首页 >技术交流 >灵魂拷问:如何优雅的与 Python 说再见?网站首页技术交流

灵魂拷问:如何优雅的与 Python 说再见?

两个月亮 2024-06-14 17:19:50
简介灵魂拷问:如何优雅的与 Python 说再见?

参考

项目描述
搜索引擎GoogleBing
Python官方文档
哔哩哔哩【python】Python的N种退出姿势,你都了解嘛?一期视频让你把每种方法都搞清楚!

描述

项目描述
PyCharm2023.1 (Professional Edition)
Python3.10.6

主动退出

在程序设计过程中,我们可能需要在某些特定的情况下主动退出 Python 程序。在 Python 中,常用的退出函数有四种,分别是 quit()exit()sys.exit() 以及 os._exit()

SystemExit

BaseException 与 Exception

BaseExceptionException 是Python中两个重要的异常基类,它们在异常处理过程中具有不同的角色和特点。

BaseException

BaseException 是所有内置异常类的基类,包括系统异常(如 SystemExitKeyboardInterrupt)和常规异常(如 Exception 的子类)。它是一个较为通用的异常基类,继承自objectBaseException的继承关系如下所示:

BaseException
 ├── SystemExit
 ├── KeyboardInterrupt
 ├── GeneratorExit
 └── Exception
     ├── StopIteration
     ├── ArithmeticError
     │   ├── FloatingPointError
     │   ├── OverflowError
     │   └── ZeroDivisionError
     ├── AssertionError
     ├── AttributeError
     ├── ...

BaseException 通常不直接用于捕获异常或抛出异常,因为它太宽泛了,会捕获或引发所有的异常。但是,它可以作为异常处理的基础,用于处理一些通用的异常情况。

Exception

Exception 是大多数常规异常的基类,它继承自 BaseException。通常情况下,我们在编写自定义异常类时会直接继承 ExceptionException 的继承关系如下所示:

Exception
 ├── StopIteration
 ├── ArithmeticError
 │   ├── FloatingPointError
 │   ├── OverflowError
 │   └── ZeroDivisionError
 ├── AssertionError
 ├── AttributeError
 ├── ...

需要注意的是,BaseExceptionException 之间的主要区别在于,BaseException是更通用的基类,包含了系统异常和常规异常,而 Exception 更加专注于常规异常,并且常用于捕获和处理异常情况。一般来说,我们在异常处理中更常使用 Exception 及其子类。

SystemExit

在Python中,SystemExit 异常是一个特殊的异常,它被引发时会导致Python解释器退出。它继承自 BaseException,而不是 Exception,这意味着它不是一个常规的异常。

SystemExit 异常通常由 sys.exit() 函数引发,或者在交互式解释器中输入Ctrl+C 组合键时由解释器自动引发。SystemExit 异常表示程序已经完成执行或者需要立即终止执行。

举个栗子

while True:
    # 获取用户输入并将其保存至变量 query 中
    query = input('>>> ')
    # 判断用户输入的值(将输入转换为小写形式)是否为 exit
    # 若是则通过抛出 SystemExit 异常来退出解释器;
    # 若不是则将用户输入显示至控制台中。
    if query.lower() == 'exit':
        raise SystemExit
    else:
        print(f'【Response】 {query}')

执行效果

>>> Hello World
【Response】 Hello World
>>> Hello China
【Response】 Hello China
>>> Exit

进程已结束,退出代码0

捕获 SystemExit

在Python中,SystemExit 异常是继承自 BaseException 的一种特殊异常。由于 SystemExit 继承自 BaseException,所以在异常处理中,我们可以通过捕获 BaseException 来捕获 SystemExit 异常。

try:
    raise SystemExit(-999)
    print('Forever Lost')
except BaseException:
    print('But I can find you')

执行效果

But I can find you

进程已结束,退出代码0
精准打击

不推荐通过捕获BaseException来捕获SystemExit异常,原因如下:

  1. 不精确捕获
    BaseException 是一个非常广泛的异常基类,它包括了系统级异常(如SystemExitKeyboardInterrupt)和常规异常(如Exception的子类)。如果我们仅仅为了捕获SystemExit而捕获BaseException,会导致捕获到其他不需要处理的异常,这 可能会掩盖程序中真正需要处理的其他异常情况

  2. 异常处理机制遭到破坏
    异常处理机制的目的是为了捕获和处理错误和异常情况。通过捕获BaseException,我们将捕获到包括系统级异常在内的所有异常,这可能导致无法区分不同类型的异常,从而 影响我们对程序执行过程中的错误和异常进行适当处理的能力

如果我们想要捕获 SystemExit 异常,应该直接捕获它,而不是通过捕获更宽泛的异常基类。这样可以保持代码的清晰性和准确性,确保我们只捕获和处理真正需要处理的异常。

举个栗子

try:
    result = 1 / 0
    raise SystemExit
except BaseException:
    print('Can you tell the difference?')

上述代码必定会抛出异常,但你知道是谁导致的异常吗?也许你能够判断出是谁引发的异常。在代码足够复杂的情况下,你还能发现吗?

执行效果

Can you tell the difference?

在 Python 中,当我们想要捕获某个异常时,应该直接捕获它,而不是通过捕获更宽泛的异常基类(如 BaseExceptionException)。这样可以缩小目标的范围,以便对产生的异常执行更有针对性的处理。

举个栗子

try:
    result = 1 / 0
    raise SystemExit
except SystemExit:
    print('SystemExit')
except ZeroDivisionError:
    print('ZeroDivisionError')

执行效果

ZeroDivisionError

退出状态码

退出状态码(Exit Status Code)是在程序退出时返回给操作系统的一个 整数值。它用于向操作系统提供关于程序退出状态的信息,通常表示程序的执行结果或发生的错误类型

退出状态码是一个约定俗成的机制,在不同的编程语言和操作系统中都存在。在大多数操作系统中,退出状态码为 0 表示程序成功执行并正常退出,非零值 则表示发生了某种错误或异常情况。具体的状态码含义可能因编程语言和操作系统而异,可以根据需要自定义状态码的含义。

一些常见的退出状态码约定如下:

  • 0
    程序成功执行并正常退出。
  • 非零值
    表示不同的错误类型或异常情况。具体的状态码取决于程序的设计和需求。

退出状态码在脚本或命令行程序中很有用,因为它 可以作为程序执行结果的指示,并可以被其他程序或脚本进行处理。在脚本中,可以通过读取其他程序的退出状态码来根据不同的情况采取相应的操作。

注:

Python 程序的退出状态码并不一定需要为整数,也可以为除整数外的其他 Python 对象。在使用非整数对象作为退出状态码时,Python 将会将该对象输出至终端中,并使用 1 作为实际的退出状态码。对此,请参考如下示例:

raise SystemExit('Hello World')

执行效果

Hello World

进程已结束,退出代码1

SystemExit 与退出状态码

在Python中,可以使用 SystemExit 异常的构造函数来指定退出状态码。SystemExit 类接受一个可选的参数作为退出状态码,并将其存储在异常对象的 code 属性中。当这个异常对象被抛出时,退出状态码会传递给调用者。

举个栗子

import sys


try:
    raise SystemExit(-999)
    print('Forever Lost')
except SystemExit as systemExit:
    print('But I can find you')
    # 通过 SystemExit 异常对象的 code 属性获取退出状态码
    # 并将该状态码作为 sys.exit() 函数的参数。
    sys.exit(systemExit.code)
    # sys.exit() 函数的作用及接收的参数与 SystemExit 类一致。

执行效果

But I can find you

进程已结束,退出代码-999

在上面的示例中,我们使用 raise 关键字抛出了一个 SystemExit 异常,并在构造函数中指定了退出状态码为 1。然后,在异常处理块中,我们通过访问code 属性获取了退出状态码,并输出了相关信息。最后,我们使用 sys.exit()函数终止程序,并将退出状态码传递给它。

如果未指定退出状态码或指定的状态码为 None,则 Python 将使用 0 作为退出状态码,表示程序正常退出。

SystemExit 与 sys.exit()

SystemExit 是 Python 中的一个异常类,它通常由 sys.exit() 函数引发。

sys.exit() 函数接受一个可选的退出状态码作为参数,该退出状态码又将作为 SystemExit() 异常类的参数以创建适合的 SystemExit 对象。sys.exit() 函数通过抛出创建的 SystemExit 对象来退出 Python 程序。

一人分饰两角

site 模块

site 模块负责设置模块搜索路径,并执行与模块和包相关的任务,以确保Python 能够找到所需的模块并正确加载它们。在通过 Python 解释器运行 Python 文件时,解释器将自动导入 site 模块以确保正确的模块搜索和加载行为。

拒绝 site

使用 -S 选项的主要效果是减少 Python 解释器启动时的启动时间和内存开销,因为解释器不需要执行 site 模块相关的任务。如果你只需要运行一些简单的 Python 脚本,并不需要自动导入任何第三方模块或执行自定义操作,那么使用 -S 选项可能是个不错的选择。

但是,在大多数情况下,你不需要使用 -S 选项。因为 site 模块是 Python 标准库的一部分,它提供了许多有用的功能,例如设置模块搜索路径和执行自定义操作。如果你使用了第三方模块或自定义操作,那么禁止导入 site 模块可能会导致你的程序出现问题。

举个栗子

我的计算机中安装了 Python 第三方模块 Numpy ,但在使用 -S 作为 Python 命令的选项后,通过 import numpy 语句导入 Numpy 模块却得到了异常信息 ModuleNotFoundError: No module named 'numpy'

(venv) PS C:UserspythonProject> python -S
Python 3.10.6 (tags/v3.10.6:9c7b4bd, Aug  1 2022, 21:53:49) [MSC v.1932 64 bit (AMD64)] on win32
>>> import numpy
Traceback (most recent call last):          
  File "<stdin>", line 1, in <module>       
ModuleNotFoundError: No module named 'numpy'
quit() 函数与 exit() 函数

在Python中,exit()quit() 函数都是由 site 模块中的 site.py 文件提供的。因此,当通过 Python 解释器运行 Python 文件时,若使用 -S 选项,exit()quit() 函数将无法正常执行。如果你尝试在使用 -S 选项的情况下调用 exit()quit() 函数,Python 将会抛出一个 NameError 异常,表示被调用函数并未被定义。对此,请参考如下示例:

(venv) PS C:UserspythonProject> python -S
Python 3.10.6 (tags/v3.10.6:9c7b4bd, Aug  1 2022, 21:53:49) [MSC v.1932 64 bit (AMD64)] on win32
>>> exit()
Traceback (most recent call last):   
  File "<stdin>", line 1, in <module>
NameError: name 'exit' is not defined
>>> quit()                           
Traceback (most recent call last):   
  File "<stdin>", line 1, in <module>
NameError: name 'quit' is not defined

builtins 模块

在Python中,builtins 模块是一个内置模块,它包含了 Python 的内置函数、异常和其他一些内置对象。这个模块将在 Python 解释器启动时自动加载,因此你可以直接访问其中定义的对象,而无需显式导入。

builtins模块提供了许多常用的内置函数和内置对象,例如:

  • print():用于输出文本到控制台。
  • input():用于从控制台读取用户输入。
  • len():用于获取对象的长度。
  • int()float()str() 等类型转换函数。
  • ExceptionTypeErrorValueError 等异常类。
  • abs()round()min()max() 等常用函数。
builtins 模块与 __builtins__

在 Python 中,__builtins__ 是一个特殊的全局变量,它指向一个字典,其中包含了内置函数、异常和对象的名称空间。在模块的全局作用域中,可以通过 __builtins__ 来访问这些内置函数和对象。

当使用 import builtins 导入 builtins 模块时,builtins 模块本身会被加载并创建一个模块对象。然后,模块对象的 __dict____dict__属性是一个字典,用于存储对象的实例变量和方法)属性会被用于初始化 __builtins__ 变量,使其指向 builtins 模块的名称空间。

因此,可以说 __builtins__builtins 模块导入至模块中的结果。通过__builtins__,可以在模块中访问到 builtins 模块提供的内置函数和对象,例如 __builtins__.print()__builtins__.len() 等。

builtins is __builtins__???

在 Python 中,多次导入同一模块的情况下,实际上只会执行一次模块的加载和初始化过程。之后的导入操作会直接引用已经加载的模块对象,而不会再次执行模块内的代码。

这意味着,多次导入同一模块并不会导致模块内的代码被重复执行。只有在第一次导入时,模块内的代码才会被执行一次,并且模块对象会被创建。之后的导入操作将简单地引用已经加载的模块对象。

Python 解释器会在运行 Python 文件时自动导入 builtins 模块,显式导入 builtins 模块将直接引用已加载的 builtins 模块对象。因此,builtins is __builtins__ 的结果将为 True

源码分析

site.setquit() 函数

Python 的内置模块 site 中的 setquit() 函数是一个用于设置quitexit 函数的函数。setquit() 函数定义了这两个函数,并将它们添加到 Python 的内置命名空间中,以使它们可以在任何地方直接使用。

在调用 setquit() 函数后,可以通过调用 quit()exit() 函数来退出 Python 解释器。实际上,quit()exit() 函数是 site.Quitter 类的实例,而 Quitter 类是定义在 site 模块中的一个内部类。

site.setquit() 的具体实现

def setquit():
    """Define new builtins 'quit' and 'exit'.

    These are objects which make the interpreter exit when called.
    The repr of each object contains a hint at how it works.

    """
    if os.sep == '\':
        eof = 'Ctrl-Z plus Return'
    else:
        eof = 'Ctrl-D (i.e. EOF)'

    builtins.quit = _sitebuiltins.Quitter('quit', eof)
    builtins.exit = _sitebuiltins.Quitter('exit', eof)
判断操作系统的类型

这段代码首先通过 os.sep == '\' 来判断当前运行 Python 解释器的操作系统的类型。判断依据如下:

类 Unit(如 Linux 和 MacOS) 操作系统使用 / 作为文件路径分隔符,而 Windows 则使用 (在上述代码中,使用 \ 来作为判断的依据是为了将 进行转义,以免该字符被解释器认为具有特殊用途来进行解释)作为文件分隔符。

标准输入流文件与 EOF

在 Python 中,标准输入流是一个已经打开的文件对象,它表示从键盘或其他标准输入设备读取的输入。在 Python 中,标准输入流文件对象通常由sys.stdin 表示。

在 Python 的交互式环境中,你可以通过向 Python 解释器发送一个 EOF 信号来退出交互式环境。EOF(End of File) 是一个特殊的字符或信号,用于表示输入流的结束。

变量 eof

eof 变量的作用是提供一个表示 EOF 即 End Of Line 的提示,以帮助用户了解如何通过组合键输入 EOF 以退出解释器。

  • 如果操作系统的路径分隔符为反斜杠 (如 Windows 系统),则将 eof 设置为'Ctrl-Z plus Return',用以提示用户可以通过敲击组合键 Ctrl-Z 后再敲击 Enter 键向 Python 发送 EOF 信号以退出 Python 交互式环境。

  • 如果操作系统的路径分隔符不是反斜杠(如 类 Unix 系统),则将 eof 设置为 'Ctrl-D (i.e. EOF)',用以提示用户可以通过敲击组合键 Ctrl-D Python 发送 EOF 信号以退出 Python 交互式环境。

_sitebuiltins.Quitter()

_sitebuiltins.Quitter() 类是 Python 内置模块 _sitebuiltins 中的一个类,它用于创建一个对象,该对象可以用于退出 Python 解释器。当调用该对象时,它将引发一个 SystemExit 异常,从而终止 Python 解释器。

_sitebuiltins.Quitter(name: str, eof: str)
参数名描述
name该参数用于指定 Quitter() 的实例对象的名称,即 Quitter().name 的取值。
eof用于提供用户在当前操作系统下如何通过敲击组合键退出 Python 交互式环境的提示。

需要注意的是,_sitebuiltins 模块中的类和函数是 Python 内部使用的,它们可能会在未来的版本中更改或删除,因此不建议在应用程序中使用这些类和函数。如果您需要退出 Python 解释器,可以使用 sys.exit() 函数,它提供了一种更可靠和可移植的方法来退出解释器。

实现自定义内置退出函数
def setquit():
    """Define new builtins 'quit' and 'exit'.

    These are objects which make the interpreter exit when called.
    The repr of each object contains a hint at how it works.

    """
    if os.sep == '\':
        eof = 'Ctrl-Z plus Return'
    else:
        eof = 'Ctrl-D (i.e. EOF)'

    builtins.quit = _sitebuiltins.Quitter('quit', eof)
    builtins.exit = _sitebuiltins.Quitter('exit', eof)
    # 在 site.py 文件中的 setquit() 函数中添加如下内容并进行保存
    builtins.byebye = _sitebuiltins.Quitter('ByeBye', eof)

其中:

builtins.byebye 用于创建 Python 内置对象 byebye ,而 _sitebuiltins.Quitter('ByeBye', eof) 则用于创建退出函数对象。

执行效果

(venv) PS C:UserspythonProject> python
Python 3.10.6 (tags/v3.10.6:9c7b4bd, Aug  1 2022, 21:53:49) [MSC v.1932 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> quit
Use quit() or Ctrl-Z plus Return to exit
>>> exit                                
Use exit() or Ctrl-Z plus Return to exit
>>> byebye                              
Use ByeBye() or Ctrl-Z plus Return to exit
>>> byebye()                              
(venv) PS C:UserspythonProject> 

在尝试复现上述示例后,请将 site.py 文件中的内容复原,避免因修改该文件后可能产生的异常。

_sitebuiltins.Quitter()

用于创建内置退出函数 quit()exit() 对象的 Quitter() 类中的源代码如下。

class Quitter(object):
    def __init__(self, name, eof):
        self.name = name
        self.eof = eof
    def __repr__(self):
        return 'Use %s() or %s to exit' % (self.name, self.eof)
    def __call__(self, code=None):
        # Shells like IDLE catch the SystemExit, but listen when their
        # stdin wrapper is closed.
        try:
            sys.stdin.close()
        except:
            pass
        raise SystemExit(code)

其中:

  • __repr__(self)
    该方法用于返回一个可打印的字符串以表示调用某个函数或以怎样触发 eof 以退出 Python 交互式环境的提示信息。

  • __call__(self, code=None)
    实现该方法以使得当前对象成为一个可调用对象,即函数对象。当调用 Quitter 对象时,会执行该方法。该方法首先尝试关闭 sys.stdin(标准输入流),然后引发 SystemExit 异常,以终止 Python 解释器。可选参数 code 可用于指定退出的返回代码。

os._exit()

系统调用

系统调用(System Call)是操作系统提供给应用程序的编程接口,用于访问操作系统核心功能和资源的一种机制。应用程序可以通过系统调用请求操作系统执行特定的任务,例如文件操作、网络通信、进程管理等。

系统调用的目的是 允许应用程序执行需要操作系统特权级别才能执行的操作,例如访问底层硬件设备或执行敏感的操作。通过系统调用,应用程序可以请求操作系统代表其执行某些任务,从而获得所需的权限和资源。

POSIX 与 NT

在 Python 中,posixnt 是两个模块,分别用于提供与 POSIX(可移植操作系统接口)和 Windows NT 操作系统相关的功能。这两个模块提供了对底层操作系统接口的访问,允许 Python 程序与操作系统进行交互和控制。

  1. posix 模块:

    • posix 模块是在 类 Unix 系统上提供的。它封装了 POSIX 标准定义的接口和函数,以便在 Python 中进行访问。
    • posix 模块提供了许多与进程管理、文件系统操作、环境变量、路径处理等相关的函数和常量。
    • 一些常见的 posix 模块函数包括:fork()(创建一个子进程)、exec()(执行一个新的程序)、kill()(发送信号给进程)、getpid()(获取当前进程 ID)等。
    • 由于 posix 模块是为 类 Unix 系统设计的,因此在 Windows 系统上可能不可用或仅提供有限的功能。
  2. nt 模块:

    • nt 模块是在 Windows NT 操作系统上提供的。它封装了 Windows 操作系统的相关功能,使得 Python 程序可以与 Windows 操作系统进行交互。
    • nt 模块提供了许多与进程管理、文件系统操作、环境变量、注册表访问等相关的函数和常量。
    • 一些常见的 nt 模块函数包括:spawnl()(启动一个新进程)、system()(执行系统命令)、getpid()(获取当前进程 ID)、getcwd()(获取当前工作目录)等。
    • nt 模块通常仅在 Windows 系统上可用。

需要注意的是,posixnt 模块中的函数和常量通常是与底层操作系统紧密相关的,使用它们需要了解底层系统接口的细节,并谨慎处理与平台相关的差异。在编写可移植的代码时,应尽量避免直接依赖于 posixnt 模块,而是使用更高级别的跨平台模块或库,如 os 模块和 shutil 模块,它们提供了对多个操作系统的统一接口。

源码分析

os._exit() 函数是通过系统调用的方式来退出 Python 的。_exit() 函数同时存在于 posixnt 模块中。在导入 os 模块后,该模块将会尝试从 posixnt 模块中导入 _exit() 函数并将该函数添加至 __all__ 中,以使得导入 os 模块的文件也能够导入 _exit() 函数。

以下内容为 os 模块中与 _exit() 函数相关的部分

if 'posix' in _names:
    name = 'posix'
    linesep = '
'
    from posix import *
    try:
        from posix import _exit
        __all__.append('_exit')
    except ImportError:
        pass
    import posixpath as path

    try:
        from posix import _have_functions
    except ImportError:
        pass

    import posix
    __all__.extend(_get_exports_list(posix))
    del posix

elif 'nt' in _names:
    name = 'nt'
    linesep = '
'
    from nt import *
    try:
        from nt import _exit
        __all__.append('_exit')
    except ImportError:
        pass
    import ntpath as path

    import nt
    __all__.extend(_get_exports_list(nt))
    del nt

    try:
        from nt import _have_functions
    except ImportError:
        pass

else:
    raise ImportError('no os specific module found')

注意事项

在使用 os._exit() 函数时,需要注意的一些细节:

  1. _exit() 函数不会执行任何 Python 解释器的清理活动,包括清理缓存、清除打开的文件等。因此,在使用 _exit() 函数时,需要确保所有需要保存的数据都已经保存,所有需要关闭的文件都已经关闭,否则可能会丢失数据或者文件被锁定。

  2. _exit() 函数的参数不可省略且必须为一个整数。_exit() 表示进程的退出状态码。在正常情况下,状态码应该为 0,表示程序正常退出。如果状态码为其他值,则表示程序异常终止。

总结

下面是对 Python 提供的四种退出函数进行的总结。

函数原理和作用使用方式
sys.exit()抛出 SystemExit 异常来退出 Python 解释器进程。sys.exit([arg])
其中,arg 是整数(默认为 1)或字符串,表示退出状态码或错误消息(arg 为字符串时,退出状态码为 1)。
quit()抛出 SystemExit 异常来退出 Python 解释器进程。quit([arg])
其中,arg 是整数(默认为 1)或字符串,表示退出状态码或错误消息(arg 为字符串时,退出状态码为 1)。
exit()抛出 SystemExit 异常来退出 Python 解释器进程。exit([arg])
其中,arg 是整数(默认为 1)或字符串,表示退出状态码或错误消息(arg 为字符串时,退出状态码为 1)。
os._exit()直接调用操作系统级别的退出函数,不抛出任何异常。os._exit(arg)
其中,arg 是整数,表示退出状态码。
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。