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.exists
和 os.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
兼容性策略
重构异常层次结构显然会更改至少某些现有代码的确切语义。虽然在不更改确切语义的情况下无法改进当前情况,但可以定义更窄的兼容性类型,我们将其称为“有用兼容性”。
为此,我们首先必须解释我们将什么称为“谨慎”和“粗心”的异常处理。“粗心”(或“天真”)代码定义为盲目捕获 OSError
、IOError
、socket.error
、mmap.error
、WindowsError
、select.error
中的任何一个而无需检查 errno
属性的代码。这是因为此类异常类型过于广泛,无法表示任何内容。它们中的任何一个都可能因各种错误条件而引发:错误的文件描述符(这通常表示编程错误)、未连接的套接字(同上)、套接字超时、文件类型不匹配、无效参数、传输失败、权限不足、不存在的目录、文件系统已满等。
(此外,某些这些异常的使用是不规则的;附录 B 公开了 select 模块的情况,它根据实现引发不同的异常)
“谨慎”代码定义为,在捕获上述任何异常时,检查 errno
属性以确定实际的错误条件并根据它采取行动的代码。
然后我们可以定义“有用兼容性”如下
- 有用兼容性不会使异常捕获更窄,但对于“粗心”异常捕获代码来说,它可以更宽泛。给定以下类型的代码片段,此 PEP 之前捕获的所有异常在此 PEP 之后也将被捕获,但反之可能不成立(因为
OSError
、IOError
等的合并意味着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,这与errno
、filename
和strerror
属性(例如,当 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)。
此外,建议包含以下异常类
ConnectionError
:ConnectionAbortedError
、ConnectionRefusedError
和ConnectionResetError
的基类
下图尝试总结提议的添加内容,以及相应的 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
等长名称)。
异常属性
为了保持有用的兼容性,这些子类仍应为超类上定义的各种异常属性(例如errno
、filename
以及可选的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 构建机器人。
OSError
和WindowsError
各自的构造函数不兼容,这曾是一个问题来源。解决方法是保留OSError
签名,并添加第四个可选参数以允许传递 Windows 错误代码(它与 POSIX errno 不同)。第四个参数存储为winerror
,其 POSIX 翻译存储为errno
。PyErr_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.gaierror
和 socket.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