Following system colour scheme - Python 增强提案 Selected dark colour scheme - Python 增强提案 Selected light colour scheme - Python 增强提案

Python 增强提案

PEP 3151 – 重构 OS 和 IO 异常层次结构

作者:
Antoine Pitrou <solipsis at pitrou.net>
BDFL 代表:
Barry Warsaw
状态:
最终
类型:
标准跟踪
创建:
2010 年 7 月 21 日
Python 版本:
3.3
历史记录:

决议:
Python-Dev 消息

目录

摘要

标准异常层次结构是 Python 语言的重要组成部分。它有两个定义特征:它既通用又选择性。通用性在于,无论上下文如何(例如,您是尝试将某些内容添加到整数、调用字符串方法还是在套接字上写入对象,对于错误的参数类型都会引发 TypeError),都可以引发并处理相同的异常类型。选择性在于,它允许用户轻松地处理(静默、检查、处理、存储或封装……)特定类型的错误情况,同时让其他错误冒泡到更高的调用上下文。例如,您可以选择捕获 ZeroDivisionErrors 而不影响其他 ArithmeticErrors(如 OverflowErrors)的默认处理方式。

本 PEP 提出对异常层次结构的一部分进行更改,以便更好地体现上述特性:与操作系统调用相关的错误(OSError、IOError、mmap.error、select.error 及其所有子类)。

基本原理

缺乏细粒度的异常

当前各种 OS 相关的异常不允许用户轻松过滤所需的故障类型。例如,考虑删除存在的文件的任务。先检查后行动(LBYL)习语存在明显的竞争条件

if os.path.exists(filename):
    os.remove(filename)

如果另一个线程或进程在调用 os.path.existsos.remove 之间创建了一个名为 filename 的文件,则不会删除它。这可能会导致应用程序中的错误,甚至安全问题。

因此,解决方案是尝试删除文件,如果文件不存在则忽略错误(一种称为“出错后求原谅,总比事前请求许可容易”或 EAFP 的习语)。谨慎的代码将如下所示(在 POSIX 和 Windows 系统下均有效)

try:
    os.remove(filename)
except OSError as e:
    if e.errno != errno.ENOENT:
        raise

甚至

try:
    os.remove(filename)
except EnvironmentError as e:
    if e.errno != errno.ENOENT:
        raise

这需要输入更多内容,并且还迫使用户记住 errno 模块中的各种神秘助记符。它增加了额外的认知负担,并且很快就会变得令人厌烦。因此,许多程序员将改写以下代码,这会过分地静默异常

try:
    os.remove(filename)
except OSError:
    pass

os.remove 不仅在文件不存在时引发 OSError,而且在其他可能的情况下也会引发 OSError(例如,文件名指向目录,或当前进程没有权限删除文件),所有这些都表明应用程序逻辑中的错误,因此不应被静默处理。程序员希望改写的内容类似于

try:
    os.remove(filename)
except FileNotFoundError:
    pass

兼容性策略

重构异常层次结构显然会更改至少某些现有代码的确切语义。虽然在不更改确切语义的情况下无法改进当前情况,但可以定义更窄的兼容性类型,我们将其称为“有用兼容性”。

为此,我们首先必须解释我们将什么称为“谨慎”和“粗心”的异常处理。“粗心”(或“天真”)代码定义为盲目捕获 OSErrorIOErrorsocket.errormmap.errorWindowsErrorselect.error 中的任何一个而无需检查 errno 属性的代码。这是因为此类异常类型过于广泛,无法表示任何内容。它们中的任何一个都可能因各种错误条件而引发:错误的文件描述符(这通常表示编程错误)、未连接的套接字(同上)、套接字超时、文件类型不匹配、无效参数、传输失败、权限不足、不存在的目录、文件系统已满等。

(此外,某些这些异常的使用是不规则的;附录 B 公开了 select 模块的情况,它根据实现引发不同的异常)

“谨慎”代码定义为,在捕获上述任何异常时,检查 errno 属性以确定实际的错误条件并根据它采取行动的代码。

然后我们可以定义“有用兼容性”如下

  • 有用兼容性不会使异常捕获更窄,但对于“粗心”异常捕获代码来说,它可以更宽泛。给定以下类型的代码片段,此 PEP 之前捕获的所有异常在此 PEP 之后也将被捕获,但反之可能不成立(因为 OSErrorIOError 等的合并意味着 except 子句抛出的范围略广)
    try:
        ...
        os.remove(filename)
        ...
    except OSError:
        pass
    
  • 有用兼容性不会改变“谨慎”异常捕获代码的行为。给定以下类型的代码片段,无论是否已实现此 PEP,都应静默或重新引发相同的错误
    try:
        os.remove(filename)
    except OSError as e:
        if e.errno != errno.ENOENT:
            raise
    

这种妥协的理由是粗心的代码无法真正得到帮助,但至少“有效”的代码不会突然引发错误并崩溃。这一点很重要,因为此类代码可能存在于用作 cron 任务或自动化系统管理程序的脚本中。

另一方面,不应惩罚谨慎的代码。实际上,本 PEP 的一个目的是简化编写谨慎代码。

步骤 1:合并异常类型

解决方案的第一步是合并现有的异常类型。建议进行以下更改

  • 将 socket.error 和 select.error 都设为 OSError 的别名
  • 将 mmap.error 设为 OSError 的别名
  • 将 WindowsError 和 VMSError 都设为 OSError 的别名
  • 将 IOError 设为 OSError 的别名
  • 将 EnvironmentError 合并到 OSError 中

这些更改中的每一个都没有保持确切的兼容性,但它们确实保留了“有用兼容性”(请参阅上面的“兼容性”部分)。

这些更改中的每一个都可以单独接受或拒绝,但当然,如果第一步完全被接受,则认为可以获得最大的影响。在这种情况下,IO 异常子层次结构将变为

+-- OSError   (replacing IOError, WindowsError, EnvironmentError, etc.)
    +-- io.BlockingIOError
    +-- io.UnsupportedOperation (also inherits from ValueError)
    +-- socket.gaierror
    +-- socket.herror
    +-- socket.timeout

理由

第一步不仅为用户提供了如 基本原理 部分所述的更简单的环境,而且还允许更好地更完整地解决 步骤 2(请参阅 先决条件)。

保留 OSError 作为通用 OS 相关异常的官方名称的基本原理是,它恰恰比 IOError 更通用。EnvironmentError 键入起来更麻烦,而且知名度也低得多。

附录 B中的调查显示,IOError 是目前标准库中最主要的错误类型。对于第三方 Python 代码,Google 代码搜索显示 IOError 在用户代码中比 EnvironmentError 常见十倍,比 OSError 常见三倍[3]。然而,由于没有计划在中期弃用 IOError,因此 OSError 的流行度较低并不是问题。

异常属性

由于 WindowsError 已合并到 OSError 中,后者在 Windows 下获得了一个winerror属性。在不具有意义的情况下,它被设置为 None,这与errnofilenamestrerror属性(例如,当 OSError 由 Python 代码直接引发时)的情况相同。

名称弃用

以下段落概述了旧异常名称可能的弃用策略。但是,目前已决定将其保留为别名。这一决定可能会在 Python 4.0 发布之前进行修改。

内置异常

通过拦截 builtins 命名空间中的所有查找来弃用旧的内置异常并不是一种简单的方法,因为这些操作对性能至关重要。我们也不能在对象级别进行操作,因为弃用的名称将被关联到未弃用的对象。

一种解决方案是在编译时识别这些名称,然后发出一个单独的LOAD_OLD_GLOBAL操作码,而不是常规的LOAD_GLOBAL。当名称不存在于全局命名空间中,而只存在于 builtins 命名空间中时,此专用操作码将处理 DeprecationWarning(或 PendingDeprecationWarning,具体取决于所决定的策略)的输出。这足以避免误报(例如,如果有人在模块中定义了自己的OSError),而漏报则很少见(例如,当有人通过builtins模块而不是直接访问OSError时)。

模块级异常

上述方法难以轻松使用,因为它需要在编译代码对象时对某些模块进行特殊处理。但是,这些名称在结构上可见度较低(它们不会出现在 builtins 命名空间中),并且知名度也较低,因此我们可能会决定让它们保留在自己的命名空间中。

步骤 2:定义其他子类

解决方案的第二步是通过定义子类来扩展层次结构,这些子类将针对特定的 errno 值引发,而不是它们的父类。哪些 errno 值需要讨论,但对现有异常匹配实践的调查(见附录 A)有助于我们提出所有值的合理子集。实际上,尝试映射所有 errno 助记符似乎是愚蠢的、毫无意义的,并且会污染根命名空间。

此外,在某些情况下,不同的 errno 值可能会引发相同的异常子类。例如,EAGAIN、EALREADY、EWOULDBLOCK 和 EINPROGRESS 都用于表示对非阻塞套接字的操作将被阻塞(因此需要稍后再次尝试)。因此,它们都可能引发相同的子类,并允许用户检查errno属性(如果需要)(参见下面的“异常属性”)。

先决条件

步骤 1是对此的一个宽松的前提条件。

前提条件,因为某些 errno 目前可以附加到不同的异常类:例如,ENOENT 可以附加到 OSError 和 IOError,具体取决于上下文。如果我们不想破坏有用的兼容性,我们就不能使except OSError(或 IOError)无法匹配在今天可以成功匹配的异常。

宽松,因为如果现有异常类未合并,我们可以决定对步骤 2 进行部分解决:例如,ENOENT 可以引发假设的 FileNotFoundError(以前引发 IOError 的地方),但在其他情况下继续引发 OSError。

如果新的子类使用多重继承来匹配所有现有的超类(或至少是 OSError 和 IOError,它们可以说是最普遍的),则可以完全消除对步骤 1 的依赖。但是,这将使层次结构更加复杂,因此用户更难理解。

新的异常类

以下初步的子类列表,以及描述和映射到它们的 errno 列表,提交讨论

  • FileExistsError:尝试创建已存在的文件或目录(EEXIST)
  • FileNotFoundError:在所有请求文件和目录但不存在的情况下(ENOENT)
  • IsADirectoryError:在目录上请求文件级操作(open()、os.remove()…)(EISDIR)
  • NotADirectoryError:在其他内容上请求目录级操作(ENOTDIR)
  • PermissionError:尝试运行操作但没有足够的访问权限 - 例如文件系统权限(EACCES、EPERM)
  • BlockingIOError:操作将在设置为非阻塞操作的对象(例如套接字)上阻塞(EAGAIN、EALREADY、EWOULDBLOCK、EINPROGRESS);这是现有的io.BlockingIOError,具有扩展的作用
  • BrokenPipeError:尝试写入管道但另一端已关闭,或尝试写入已关闭写入的套接字(EPIPE、ESHUTDOWN)
  • InterruptedError:系统调用被传入信号中断(EINTR)
  • ConnectionAbortedError:连接尝试被对等方中止(ECONNABORTED)
  • ConnectionRefusedError:连接被对等方拒绝(ECONNREFUSED)
  • ConnectionResetError:连接被对等方重置(ECONNRESET)
  • TimeoutError:连接超时(ETIMEDOUT);这可以被重新定义为通用的超时异常,替换socket.timeout,并且对其他类型的超时(例如 Lock.acquire() 中的超时)也很有用
  • ChildProcessError:子进程操作失败(ECHILD);这主要由 wait() 函数族引发。
  • ProcessLookupError:给定的进程(例如,由其进程 ID 标识)不存在(ESRCH)。

此外,建议包含以下异常类

  • ConnectionErrorConnectionAbortedErrorConnectionRefusedErrorConnectionResetError的基类

下图尝试总结提议的添加内容,以及相应的 errno 值(如果适用)。未显示子层次结构的根(OSError,假设步骤 1完全被接受)

+-- BlockingIOError        EAGAIN, EALREADY, EWOULDBLOCK, EINPROGRESS
+-- ChildProcessError                                          ECHILD
+-- ConnectionError
    +-- BrokenPipeError                              EPIPE, ESHUTDOWN
    +-- ConnectionAbortedError                           ECONNABORTED
    +-- ConnectionRefusedError                           ECONNREFUSED
    +-- ConnectionResetError                               ECONNRESET
+-- FileExistsError                                            EEXIST
+-- FileNotFoundError                                          ENOENT
+-- InterruptedError                                            EINTR
+-- IsADirectoryError                                          EISDIR
+-- NotADirectoryError                                        ENOTDIR
+-- PermissionError                                     EACCES, EPERM
+-- ProcessLookupError                                          ESRCH
+-- TimeoutError                                            ETIMEDOUT

命名

可能会出现各种命名争议。其中之一是所有异常类名是否都应该以“Error”结尾。支持者认为这与其余异常层次结构保持一致,反对者认为这过于冗长(特别是对于ConnectionAbortedError等长名称)。

异常属性

为了保持有用的兼容性,这些子类仍应为超类上定义的各种异常属性(例如errnofilename以及可选的winerror)设置适当的值。

实现

由于建议根据errno的值引发子类,因此扩展模块(标准或第三方)中应该不需要进行很少或根本不需要进行更改。

第一种可能性是调整PyErr_SetFromErrno()函数族(在 Windows 下为PyErr_SetFromWindowsErr()),以引发相应的 OSError 子类。但是,这无法涵盖使用以下习惯用法(在Lib/tempfile.py中可见)直接引发 OSError 的 Python 代码。

raise IOError(_errno.EEXIST, "No usable temporary file name found")

Marc-Andre Lemburg 提出的第二种可能性是调整OSError.__new__以实例化相应的子类。这样做的好处还在于涵盖了上述 Python 代码。

可能的异议

命名空间污染

使异常层次结构更细粒度会导致根(或 builtins)命名空间更大。但是,这需要适度,因为

  • 仅建议添加少量额外的类;
  • 虽然标准异常类型位于根命名空间中,但它们在视觉上通过使用 CamelCase 约定与众不同,而几乎所有其他内置类型都使用小写命名(除了 True、False、None、Ellipsis 和 NotImplemented)

另一种方法是提供一个包含更细粒度异常的单独模块,但这会违背鼓励谨慎代码而不是粗心代码的目的,因为用户首先必须导入新模块,而不是使用已可访问的名称。

早期讨论

虽然这是第一次提出此类正式提案,但该想法在过去曾得到非正式的支持[1];更细粒度的异常类和 OSError 与 IOError 的合并。

仅删除 WindowsError 已作为另一个 PEP的一部分进行过讨论和拒绝,但似乎存在共识,即与 OSError 的区别没有意义。这至少支持将其与 OSError 关联。

实现

参考实现已集成到 Python 3.3 中。它以前在http://hg.python.org/features/pep-3151/pep-3151分支中开发,并在错误跟踪器上进行跟踪http://bugs.python.org/issue12555。它已在各种系统上成功测试:Linux、Windows、OpenIndiana 和 FreeBSD 构建机器人。

OSErrorWindowsError各自的构造函数不兼容,这曾是一个问题来源。解决方法是保留OSError签名,并添加第四个可选参数以允许传递 Windows 错误代码(它与 POSIX errno 不同)。第四个参数存储为winerror,其 POSIX 翻译存储为errnoPyErr_SetFromWindowsErr*函数已调整为使用正确的构造函数调用。

PyErr_SetExcFromWindowsErr* 函数使用 OSError 而不是 WindowsError 调用时,会出现一个轻微的复杂情况:异常对象的 errno 属性将存储 Windows 错误代码(例如,109 代表 ERROR_BROKEN_PIPE),而不是其 POSIX 转换(例如,32 代表 EPIPE),而现在它存储的是后者。对于非套接字错误代码,这种情况仅发生在私有的 _multiprocessing 模块中,该模块不存在兼容性问题。

注意

对于套接字错误,由 errno 模块反映的“POSIX errno”在数值上等于 Windows 套接字错误代码,该代码由 WSAGetLastError 系统调用返回。

>>> errno.EWOULDBLOCK
10035
>>> errno.WSAEWOULDBLOCK
10035

可能的替代方案

模式匹配

另一种可能性是在捕获异常时引入高级模式匹配语法。例如

try:
    os.remove(filename)
except OSError as e if e.errno == errno.ENOENT:
    pass

此提案存在几个问题

  • 它引入了新的语法,作者认为与重新设计异常层次结构相比,这是一个更大的变化。
  • 它没有显著减少输入工作量。
  • 它没有减轻程序员记住 errno 助记符的负担。

此 PEP 忽略的异常

此 PEP 忽略了 EOFError,它在各种协议和文件格式实现(例如 GzipFile)中表示输入流被截断。 EOFError 与操作系统或 IO 无关,它是在更高层级引发的逻辑错误。

此 PEP 还忽略了 SSLError,它由 ssl 模块引发,以传播由 OpenSSL 库发出的错误信号。理想情况下,SSLError 将受益于类似但独立的处理,因为它为错误类型定义了自己的常量(ssl.SSL_ERROR_WANT_READ 等)。在 Python 3.2 中,当 SSLError 表示套接字超时时,它已经被替换为 socket.timeout(请参阅 问题 10272)。

最后,socket.gaierrorsocket.herror 的命运尚未确定。虽然它们应该拥有不太模糊的名称,但这可以与异常层次结构重组工作分开处理。

附录 A:常见错误码调查

这是标准库及其测试中检查的各种 errno 助记符的快速清单,作为 except 子句的一部分。

与 OSError 相关的常见错误码

  • EBADF:错误的文件描述符(通常表示文件描述符已关闭)
  • EEXIST:文件或目录已存在
  • EINTR:中断的函数调用
  • EISDIR:是一个目录
  • ENOTDIR:不是一个目录
  • ENOENT:没有这样的文件或目录
  • EOPNOTSUPP:套接字不支持的操作(可能与现有的 io.UnsupportedOperation 混淆)
  • EPERM:操作不被允许(例如,使用 os.setuid() 时)

与 IOError 相关的常见错误码

  • EACCES:权限被拒绝(对于文件系统操作)
  • EBADF:错误的文件描述符(使用 select.epoll);对只写 GzipFile 进行读取操作,反之亦然
  • EBUSY:设备或资源繁忙
  • EISDIR:是一个目录(尝试打开时)
  • ENODEV:没有这样的设备
  • ENOENT:没有这样的文件或目录(尝试打开时)
  • ETIMEDOUT:连接超时

与 socket.error 相关的常见错误码

所有这些错误也可能与普通的 IOError 相关联,例如,当在套接字的文件描述符上调用 read() 时。

  • EAGAIN:资源暂时不可用(在非阻塞套接字调用期间,除了 connect())
  • EALREADY:连接已在进行中(在非阻塞 connect() 期间)
  • EINPROGRESS:操作正在进行中(在非阻塞 connect() 期间)
  • EINTR:中断的函数调用
  • EISCONN:套接字已连接
  • ECONNABORTED:连接被对等方中止(在 accept() 调用期间)
  • ECONNREFUSED:连接被对等方拒绝
  • ECONNRESET:连接被对等方重置
  • ENOTCONN:套接字未连接
  • ESHUTDOWN:传输端点关闭后无法发送
  • EWOULDBLOCK:与 EAGAIN 相同的原因

与 select.error 相关的常见错误码

  • EINTR:中断的函数调用

附录 B:已引发的 OS 和 IO 错误调查

关于 VMSError

VMSError 完全未被解释器核心和标准库使用。它是由 Jean-François Piéronne 于 2002 年提交的 OpenVMS 补丁的一部分 [4];包含 VMSError 的动机是它可以由第三方包引发。

解释器核心

处理 PYTHONSTARTUP 会引发 IOError(但错误会被丢弃)

$ PYTHONSTARTUP=foox ./python
Python 3.2a0 (py3k:82920M, Jul 16 2010, 22:53:23)
[GCC 4.4.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
Could not open PYTHONSTARTUP
IOError: [Errno 2] No such file or directory: 'foox'

PyObject_Print() 当 ferror() 信号在 FILE * 参数(在源代码树中,始终是 stdout 或 stderr)上出现错误时,会引发 IOError。

使用 mbcs 编码进行 Unicode 编码和解码可能会在某些错误情况下引发 WindowsError。

标准库

bz2

始终引发 IOError(OSError 未使用)

>>> bz2.BZ2File("foox", "rb")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: [Errno 2] No such file or directory
>>> bz2.BZ2File("LICENSE", "rb").read()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: invalid data stream
>>> bz2.BZ2File("/tmp/zzz.bz2", "wb").read()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: file is not ready for reading

curses

未检查。

dbm.gnu, dbm.ndbm

_dbm.error 和 _gdbm.error 继承自 IOError

>>> dbm.gnu.open("foox")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
_gdbm.error: [Errno 2] No such file or directory

fcntl

始终引发 IOError(OSError 未使用)。

imp 模块

对于错误的文件描述符,引发 IOError

>>> imp.load_source("foo", "foo", 123)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: [Errno 9] Bad file descriptor

io 模块

在 Unix 下尝试打开目录时引发 IOError

>>> open("Python/", "r")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: [Errno 21] Is a directory: 'Python/'

对于不支持的操作,引发 IOError 或 io.UnsupportedOperation(后者继承自前者)

>>> open("LICENSE").write("bar")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: not writable
>>> io.StringIO().fileno()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
io.UnsupportedOperation: fileno
>>> open("LICENSE").seek(1, 1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: can't do nonzero cur-relative seeks

当底层 I/O 层行为异常(即违反其预期实现的 API)时,引发 IOError 或 TypeError。

当底层操作系统资源变得无效时,引发 IOError

>>> f = open("LICENSE")
>>> os.close(f.fileno())
>>> f.read()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: [Errno 9] Bad file descriptor

……或用于特定于实现的优化

>>> f = open("LICENSE")
>>> next(f)
'A. HISTORY OF THE SOFTWARE\n'
>>> f.tell()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: telling position disabled by next() call

当对非阻塞对象的调用将被阻塞时,引发 BlockingIOError(继承自 IOError)。

mmap

在 Unix 下,始终引发自己的 mmap.error(继承自 EnvironmentError)

>>> mmap.mmap(123, 10)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
mmap.error: [Errno 9] Bad file descriptor
>>> mmap.mmap(os.open("/tmp", os.O_RDONLY), 10)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
mmap.error: [Errno 13] Permission denied

然而,在 Windows 下,它主要引发 WindowsError(源代码还显示了一些 mmap.error 的出现情况)

>>> fd = os.open("LICENSE", os.O_RDONLY)
>>> m = mmap.mmap(fd, 16384)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
WindowsError: [Error 5] Accès refusé
>>> sys.last_value.errno
13
>>> errno.errorcode[13]
'EACCES'

>>> m = mmap.mmap(-1, 4096)
>>> m.resize(16384)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
WindowsError: [Error 87] Paramètre incorrect
>>> sys.last_value.errno
22
>>> errno.errorcode[22]
'EINVAL'

multiprocessing

未检查。

os / posix

os(或 posix)模块始终引发 OSError,除了在 Windows 下,可能会引发 WindowsError。

ossaudiodev

始终引发 IOError(OSError 未使用)

>>> ossaudiodev.open("foo", "r")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: [Errno 2] No such file or directory: 'foo'

readline

在各种文件处理函数中引发 IOError

>>> readline.read_history_file("foo")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: [Errno 2] No such file or directory
>>> readline.read_init_file("foo")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: [Errno 2] No such file or directory
>>> readline.write_history_file("/dev/nonexistent")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: [Errno 13] Permission denied

select

  • select() 和 poll 对象引发 select.error,它不继承自任何内容(但 poll.modify() 引发 IOError);
  • epoll 对象引发 IOError;
  • kqueue 对象引发 OSError 和 IOError。

作为旁注,不从 EnvironmentError 派生意味着 select.error 没有有用的 errno 属性。用户代码必须检查 args[0]

>>> signal.alarm(1); select.select([], [], [])
0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
select.error: (4, 'Interrupted system call')
>>> e = sys.last_value
>>> e
error(4, 'Interrupted system call')
>>> e.errno == errno.EINTR
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'error' object has no attribute 'errno'
>>> e.args[0] == errno.EINTR
True

signal

signal.ItimerError 继承自 IOError。

socket

socket.error 继承自 IOError。

sys

sys.getwindowsversion() 如果 GetVersionEx() 调用失败,则使用伪造的错误号引发 WindowsError。

time

对于 time.time() 和 time.sleep() 中的内部错误,引发 IOError。

zipimport

zipimporter.get_data() 可以引发 IOError。

致谢

Alyssa Coghlan 提供了重要的输入。

参考文献


来源:https://github.com/python/peps/blob/main/peps/pep-3151.rst

上次修改时间:2023-10-11 12:05:51 GMT