CPython errno 模块完全指南:标准系统错误符号、errorcode 字典与 OSError 异常映射
【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython
本篇技术指南以 CPython 标准库文档 Doc/library/errno.rst 为主体,系统讲解errno模块如何对外暴露底层操作系统(Linux、macOS、Windows、WASI 等)的 errno 系统错误符号。读完本文,你将掌握:errno 符号与整数值的对应关系、errno.errorcode反向字典的使用方法、如何通过os.strerror把数值错误码翻译为可读错误消息,以及每个 errno 符号在 CPython 中如何自动映射到对应的OSError子类异常,从而写出健壮、可移植的系统级错误处理代码。
errno 模块是什么
errno是 CPython 的一个内建标准模块,作用是把操作系统底层的“errno 系统符号”原样暴露给 Python 层。每个符号对应的值就是操作系统使用的那个整数,例如在大多数系统上errno.ENOENT等于整数2。模块本身不新增任何业务语义,它是对平台 C 头文件<errno.h>中常量的一次系统化、按平台条件导出的镜像。
从实现看,该模块由 Modules/errnomodule.c 提供,其模块文档字符串(见 errnomodule.c)明确说明了这一用途:
The value of each symbol is the corresponding integer value, e.g., on most systems,
errno.ENOENTequals the integer 2. The dictionaryerrno.errorcodemaps numeric codes to symbol names...
源码中的名称与英文注释“borrowed from linux/include/errno.h”(见 errnomodule.c),这一点与官方文档 errno.rst 中“names and descriptions are borrowed fromlinux/include/errno.h, which should be all-inclusive”的表述互相印证——即 errno 符号表设计上以 Linux 的 errno.h 为全集基准,力求“包罗万象”,同时兼顾 Solaris、macOS 等平台的独有符号。
核心数据对象:errorcode 字典
errno模块中最重要的数据对象是errno.errorcode,它是一个提供从 errno 整数值到字符串名称映射的字典。例如:
>>> import errno >>> errno.errorcode[errno.EPERM] # 取 EPERM 对应的字符串名称 'EPERM'其用途与模块内的符号属性正好互补:
errno.EPERM(属性访问)——由符号名得到整数值;errno.errorcode[errno.EPERM](字典查询)——由整数值得到符号名字符串。
在 C 实现中,errno模块的表结构正是“一个模块属性字典 + 一个 errorcode 字典”双向插入构建的。核心逻辑位于 errnomodule.c:
static int _add_errcode(PyObject *module_dict, PyObject *error_dict, const char *name_str, int code_int) { ... /* insert in modules dict */ if (PyDict_SetItem(module_dict, name, code) < 0) { ... } /* insert in errorcode dict */ if (PyDict_SetItem(error_dict, code, name) < 0) { ... } ... }即每个符号通过add_errcode("ENOENT", ENOENT, "No such file or directory")这样的宏(见 errnomodule.c)同时写入两处,errno.errorcode则在模块执行入口errno_exec中先创建再填充(见 errnomodule.c)。
该模块在标准库中的配套测试 Lib/test/test_errno.py 对errorcode字典做了两条一致性约束:
- errorcode 完整性:
errorcode中每个字符串值都必须能作为属性存在于errno模块上(见 test_errno.py); - 属性双向覆盖:模块
__dict__中每一个全大写命名的属性,其整数值也都必须能反向在errorcode字典中查到(见 test_errno.py)。
这两条测试保证了“符号→数值→符号名”的双向查找永远不会断裂。
平台条件导出:并非所有符号在所有平台都可用
官方文档 errno.rst 明确提醒:在当前平台上未被使用的符号,模块就不会定义。也就是说errno的成员集合随操作系统/编译环境变化,同一份代码在不同平台看到的dir(errno)并不相同。
具体有哪些符号可用,可以通过下面两种方式查询:
import errno # 方式一:errorcode 的键集合即当前平台定义的全部符号值 print(errno.errorcode.keys()) # 方式二:直接列出模块中定义的全大写符号名 names = [k for k in errno.__dict__ if k.isupper()] print(len(names), "symbols defined on this platform")这种按平台裁剪的机制在源码中体现得十分直接:C 代码中每个符号都包裹在条件编译#ifdef/#endif内(例如#ifdef ENODEV、#ifdef EHOSTUNREACH,见 errnomodule.c),只有当前平台的系统头文件定义了该 errno 常量才会被注册进模块。因此:
- 在 Linux 上可看到完整的
EPERM~ERFKILL及 Linux 特有的ELOCKUNMAPPED、ENOTACTIVE等; - macOS 额外携带
EAUTH、EBADARCH、EBADEXEC、EBADMACHO、EBADRPC、EPROCLIM等“MacOSX specific errnos”(见 errnomodule.c); - Solaris 平台补充
ECANCELED、ENOTSUP、EOWNERDEAD、ENOTRECOVERABLE等“Solaris-specific errnos”(见 errnomodule.c); - WASI 平台特有的
ENOTCAPABLE(“Capabilities insufficient”)在有#ifdef ENOTCAPABLE时也会注册(见 errnomodule.c)。
Windows 平台:WSA 套接字错误码的桥接
Windows 上errno.h与 Winsock 的错误码体系并不一致。为了让 Python 的 errno 符号保持跨平台一致语义,源码在 MS_WINDOWS 分支下做了特殊处理(见 errnomodule.c):先#undef掉那些在 VS2010 之后才被塞进errno.h、但实际应优先使用 WSA 等价值的常量(包括EADDRINUSE、ECONNRESET、ETIMEDOUT、EWOULDBLOCK等约 25 个),随后在这些符号不存在于errno.h时改用对应WSAE*错误码来注册,例如:
#ifdef EHOSTUNREACH add_errcode("EHOSTUNREACH", EHOSTUNREACH, "No route to host"); #else #ifdef WSAEHOSTUNREACH add_errcode("EHOSTUNREACH", WSAEHOSTUNREACH, "No route to host"); #endif #endif(见 errnomodule.c)。因此 Windows 上errno.EHOSTUNREACH的值实际来自 Winsock 的WSAEHOSTUNREACH,从而保证 Python 层网络代码只需面对统一的 errno 命名。
符号速查:errno 成员及含义总表
官方文档 errno.rst 给出了当前版本的完整符号列表。由于符号是按平台条件导出的,下表中的可用性以当前平台为准,可通过errno.errorcode.keys()实测。表中凡是文档标注了对应异常类型的条目,CPython 在抛出该 errno 时会自动转换为相应的OSError子类(详见下一节“异常自动映射”)。
通用核心符号(几乎所有 POSIX 平台可用)
| errno 符号 | 整型值含义(文档原文描述) | 自动映射的内置异常 |
|---|---|---|
EPERM | Operation not permitted(操作不被允许) | PermissionError |
ENOENT | No such file or directory(无此文件或目录) | FileNotFoundError |
ESRCH | No such process(无此进程) | ProcessLookupError |
EINTR | Interrupted system call(系统调用被中断) | InterruptedError |
EIO | I/O error(I/O 错误) | — |
ENXIO | No such device or address(无此设备或地址) | — |
E2BIG | Arg list too long(参数列表过长) | — |
ENOEXEC | Exec format error(可执行文件格式错误) | — |
EBADF | Bad file number(文件描述符错误) | — |
ECHILD | No child processes(无子进程) | ChildProcessError |
EAGAIN | Try again(资源暂不可用,请重试) | BlockingIOError |
ENOMEM | Out of memory(内存不足) | — |
EACCES | Permission denied(权限被拒绝) | PermissionError |
EFAULT | Bad address(非法地址) | — |
ENOTBLK | Block device required(需要块设备) | — |
EBUSY | Device or resource busy(设备或资源忙) | — |
EEXIST | File exists(文件已存在) | FileExistsError |
EXDEV | Cross-device link(跨设备链接) | — |
ENODEV | No such device(无此设备) | — |
ENOTDIR | Not a directory(不是目录) | NotADirectoryError |
EISDIR | Is a directory(是目录) | IsADirectoryError |
EINVAL | Invalid argument(参数无效) | — |
ENFILE | File table overflow(系统文件表溢出) | — |
EMFILE | Too many open files(打开的文件过多) | — |
ENOTTY | Not a typewriter(不是终端设备) | — |
ETXTBSY | Text file busy(文本文件正被占用) | — |
EFBIG | File too large(文件过大) | — |
ENOSPC | No space left on device(设备空间不足) | — |
ESPIPE | Illegal seek(非法定位) | — |
EROFS | Read-only file system(只读文件系统) | — |
EMLINK | Too many links(链接数过多) | — |
EPIPE | Broken pipe(管道破裂) | BrokenPipeError |
EDOM | Math argument out of domain of func(数学参数超出定义域) | — |
ERANGE | Math result not representable(数学结果无法表示) | — |
EDEADLK | Resource deadlock would occur(将发生资源死锁) | — |
ENAMETOOLONG | File name too long(文件名过长) | — |
ENOLCK | No record locks available(无可用记录锁) | — |
ENOSYS | Function not implemented(功能未实现) | — |
ENOTEMPTY | Directory not empty(目录非空) | — |
ELOOP | Too many symbolic links encountered(符号链接层数过多) | — |
EWOULDBLOCK | Operation would block(操作会阻塞) | BlockingIOError |
消息队列 / 流 / STREAMS 相关符号
| errno 符号 | 整型值含义 | 备注 |
|---|---|---|
ENOMSG | No message of desired type | — |
EIDRM | Identifier removed | — |
ECHRNG | Channel number out of range | — |
EL2NSYNC | Level 2 not synchronized | — |
EL3HLT | Level 3 halted | — |
EL3RST | Level 3 reset | — |
ELNRNG | Link number out of range | — |
EUNATCH | Protocol driver not attached | — |
ENOCSI | No CSI structure available | — |
EL2HLT | Level 2 halted | — |
EBADE | Invalid exchange | — |
EBADR | Invalid request descriptor | — |
EXFULL | Exchange full | — |
ENOANO | No anode | — |
EBADRQC | Invalid request code | — |
EBADSLT | Invalid slot | — |
EDEADLOCK | File locking deadlock error | 与EDEADLK语义相近 |
EBFONT | Bad font file format | — |
ENOSTR | Device not a stream | — |
ENODATA | No data available | — |
ETIME | Timer expired | — |
ENOSR | Out of streams resources | — |
ENONET | Machine is not on the network | — |
ENOPKG | Package not installed | — |
EREMOTE | Object is remote | — |
ENOLINK | Link has been severed | — |
EADV | Advertise error | — |
ESRMNT | Srmount error | — |
ECOMM | Communication error on send | — |
EPROTO | Protocol error | — |
EMULTIHOP | Multihop attempted | — |
EDOTDOT | RFS specific error | — |
EBADMSG | Not a data message | — |
EOVERFLOW | Value too large for defined data type | — |
ENOTUNIQ | Name not unique on network | — |
EBADFD | File descriptor in bad state | — |
EREMCHG | Remote address changed | — |
ELIBACC | Can not access a needed shared library | — |
ELIBBAD | Accessing a corrupted shared library | — |
ELIBSCN | .lib section in a.out corrupted | — |
ELIBMAX | Attempting to link in too many shared libraries | — |
ELIBEXEC | Cannot exec a shared library directly | — |
EILSEQ | Illegal byte sequence(非法字节序列) | — |
ERESTART | Interrupted system call should be restarted | — |
ESTRPIPE | Streams pipe error | — |
EUSERS | Too many users | — |
网络 / 套接字相关符号
| errno 符号 | 整型值含义 | 自动映射的内置异常 |
|---|---|---|
ENOTSOCK | Socket operation on non-socket | — |
EDESTADDRREQ | Destination address required | — |
EMSGSIZE | Message too long | — |
EPROTOTYPE | Protocol wrong type for socket | — |
ENOPROTOOPT | Protocol not available | — |
EPROTONOSUPPORT | Protocol not supported | — |
ESOCKTNOSUPPORT | Socket type not supported | — |
EOPNOTSUPP | Operation not supported on transport endpoint | — |
ENOTSUP | Operation not supported | 3.2 起加入 |
EPFNOSUPPORT | Protocol family not supported | — |
EAFNOSUPPORT | Address family not supported by protocol | — |
EADDRINUSE | Address already in use(地址已被占用) | — |
EADDRNOTAVAIL | Cannot assign requested address | — |
ENETDOWN | Network is down | — |
ENETUNREACH | Network is unreachable | — |
ENETRESET | Network dropped connection because of reset | — |
ECONNABORTED | Software caused connection abort | ConnectionAbortedError |
ECONNRESET | Connection reset by peer(对端重置连接) | ConnectionResetError |
ENOBUFS | No buffer space available | — |
EISCONN | Transport endpoint is already connected | — |
ENOTCONN | Transport endpoint is not connected | — |
ESHUTDOWN | Cannot send after transport endpoint shutdown | BrokenPipeError |
ETOOMANYREFS | Too many references: cannot splice | — |
ETIMEDOUT | Connection timed out(连接超时) | TimeoutError |
ECONNREFUSED | Connection refused(连接被拒绝) | ConnectionRefusedError |
EHOSTDOWN | Host is down | — |
EHOSTUNREACH | No route to host | — |
EALREADY | Operation already in progress | BlockingIOError |
EINPROGRESS | Operation now in progress | BlockingIOError |
文件系统高级特性 / 现代平台新增符号
| errno 符号 | 整型值含义 | 版本 / 平台备注 |
|---|---|---|
ESTALE | Stale NFS file handle(NFS 文件句柄失效) | NFS 相关 |
EUCLEAN | Structure needs cleaning | — |
ENOTNAM | Not a XENIX named type file | — |
ENAVAIL | No XENIX semaphores available | — |
EISNAM | Is a named type file | — |
EREMOTEIO | Remote I/O error | — |
EDQUOT | Quota exceeded(磁盘配额超限) | — |
EQFULL | Interface output queue is full | 3.11 起加入 |
ENOMEDIUM | No medium found | — |
EMEDIUMTYPE | Wrong medium type | — |
ENOKEY | Required key not available | — |
EKEYEXPIRED | Key has expired | — |
EKEYREVOKED | Key has been revoked | — |
EKEYREJECTED | Key was rejected by service | — |
ERFKILL | Operation not possible due to RF-kill | — |
ELOCKUNMAPPED | Locked lock was unmapped | Linux 特有 |
ENOTACTIVE | Facility is not active | Linux 特有 |
EAUTH | Authentication error | 3.2 起加入,macOS/BSD |
EBADARCH | Bad CPU type in executable | 3.2 起加入,macOS |
EBADEXEC | Bad executable (or shared library) | 3.2 起加入,macOS |
EBADMACHO | Malformed Mach-o file | 3.2 起加入,macOS |
EBADRPC | RPC struct is bad | 3.2 起加入,macOS |
EDEVERR | Device error | 3.2 起加入,macOS |
EFTYPE | Inappropriate file type or format | 3.2 起加入 |
ENEEDAUTH | Need authenticator | 3.2 起加入,macOS |
ENOATTR | Attribute not found | 3.2 起加入,macOS |
ENOPOLICY | Policy not found | 3.2 起加入,macOS |
EPROCLIM | Too many processes | 3.2 起加入,macOS |
EPROCUNAVAIL | Bad procedure for program | 3.2 起加入,macOS |
EPROGMISMATCH | Program version wrong | 3.2 起加入,macOS |
EPROGUNAVAIL | RPC prog. not avail | 3.2 起加入,macOS |
EPWROFF | Device power is off | 3.2 起加入,macOS |
ERPCMISMATCH | RPC version wrong | 3.2 起加入,macOS |
ESHLIBVERS | Shared library version mismatch | 3.2 起加入,macOS |
ECANCELED | Operation canceled | 3.2 起加入 |
EOWNERDEAD | Owner died | 3.2 起加入 |
ENOTRECOVERABLE | State not recoverable | 3.2 起加入 |
ENOTCAPABLE | Capabilities insufficient | 3.11.1 起加入;WASI、FreeBSD |
EHWPOISON | Memory page has hardware error(内存页硬件故障) | 3.14 起加入 |
各符号的.. versionadded::标注与其在源码中的注释一致:3.11 引入EQFULL,3.14 引入EHWPOISON,3.11.1 引入 WASI/FreeBSD 专用的ENOTCAPABLE,而 macOS/BSD 系列(EAUTH至ECANCELED、EOWNERDEAD、ENOTRECOVERABLE等)大多在 3.2 起随平台支持加入。
errno 与 OSError 子类的自动映射
文档中大量条目标注了“This error is mapped to the exception :exc:PermissionError”等映射说明,这是errno模块最具工程价值的部分:CPython 会在解释器内部维护 errno 整数值 →OSError子类的映射表(errnomap),当底层 C 调用因某个 errno 失败时,Python 层抛出的不是笼统的OSError,而是更具体的异常子类,便于精细化except。
该映射表在 Objects/exceptions.c 的_PyOSError_Init中通过ADD_ERRNO(TYPE, CODE)宏逐一建立,与文档的标注一一对应:
| errno | 映射到的异常子类 | 异常含义 |
|---|---|---|
EAGAIN/EALREADY/EINPROGRESS/EWOULDBLOCK | BlockingIOError | 操作会阻塞(非阻塞 I/O 场景) |
EPIPE、ESHUTDOWN(有定义时) | BrokenPipeError | 管道破裂/已关闭连接后发送 |
ECHILD | ChildProcessError | 无子进程可等待 |
ECONNABORTED | ConnectionAbortedError | 连接被中止 |
ECONNREFUSED | ConnectionRefusedError | 连接被拒绝 |
ECONNRESET | ConnectionResetError | 连接被对端重置 |
EEXIST | FileExistsError | 文件已存在 |
ENOENT | FileNotFoundError | 文件/目录不存在 |
EISDIR | IsADirectoryError | 目标是目录 |
ENOTDIR | NotADirectoryError | 不是目录 |
EINTR | InterruptedError | 系统调用被信号中断 |
EACCES/EPERM、ENOTCAPABLE(有定义时) | PermissionError | 权限不足 |
ESRCH | ProcessLookupError | 进程不存在 |
ETIMEDOUT(含 WindowsWSAETIMEDOUT) | TimeoutError | 操作超时 |
需要注意的工程要点:
- 映射仅对
OSError本身生效。errnomap的查找逻辑位于 OSError 的类型初始化与构造路径(见 Objects/exceptions.c),只有以OSError(...)方式构造、并且 type 恰好是OSError时才会把errno数值“翻译”成最匹配的子类。而异常机制每次最终抛出时都会经过这一映射。 errnomap是逐解释器(interpreter)独立的,由struct _Py_exc_state持有(见 Objects/exceptions.c),多解释器环境下互不干扰。- 文档中“symbols available can include”是保守表述——最终以
errno.errorcode.keys()实测结果为准,切勿在代码里硬编码某个平台可能没有的符号(如直接在 Windows 上依赖 Linux 的ELOCKUNMAPPED),应先hasattr(errno, 'ELOCKUNMAPPED')判断。
从数值错误码到异常/消息的完整翻译链路
把三者串起来,一次完整的“底层错误 → Python 异常”翻译链路是:
- C 层系统调用失败后设置
errno(如ENOENT = 2); - 解释器依据 Objects/exceptions.c 的
errnomap找到对应OSError子类(FileNotFoundError)并抛出; - 若代码想拿到可读消息,可用
os.strerror(errno)。os.strerror(code)定义在 Doc/library/os.rst,对应 C 的strerror(),在未知错误码可能返回NULL的平台上会回退处理。
import errno, os print(errno.ENOENT) # 2(大多数系统) print(errno.errorcode[errno.ENOENT]) # 'ENOENT' print(os.strerror(errno.ENOENT)) # 'No such file or directory'(随系统 locale)官方文档强调,把数值错误码翻译成错误消息应当使用os.strerror,而不是errno模块本身——errno只负责“符号⇄数值”的映射,消息文本由操作系统提供。
实战:用 errno 写出可移植的错误处理
场景一:区分“文件不存在”和“权限不足”
import errno import os try: with open("/etc/shadow") as f: # 典型权限受限文件 print(f.read()) except PermissionError: print("没有权限读取该文件") except FileNotFoundError: print("文件不存在")这正是errnomap自动映射的价值:你不需要每次比较e.errno == errno.EACCES,except PermissionError就能精准命中EACCES、EPERM引发的失败。
场景二:非阻塞 I/O 的“资源暂时不可用”判断
网络编程中EAGAIN/EWOULDBLOCK(非阻塞模式下暂时无数据可读)在 CPython 中被统一映射为BlockingIOError:
import errno import socket sock = socket.socket() sock.setblocking(False) try: sock.connect(("example.com", 80)) except BlockingIOError as e: # connect 尚未完成,errno 可能是 EINPROGRESS / EALREADY if e.errno in (errno.EINPROGRESS, errno.EALREADY): print("连接进行中,可交由事件循环稍后检查")场景三:按 errno 数值做跨平台兜底
当目标异常子类覆盖不全、或需要处理“无专有子类”的 errno(如ENOSPC、ENOMEM)时,仍可退回检查e.errno:
import errno try: with open("/tmp/bigfile", "wb") as f: f.write(b"\0" * (10**12)) except OSError as e: if e.errno == errno.ENOSPC: print("磁盘空间不足,请清理后再试") elif e.errno == errno.EDQUOT: print("超出磁盘配额") else: print(f"其他 I/O 错误 errno={e.errno}: {e.strerror}")跨平台前务必用hasattr做能力探测,例如 WASI/FreeBSD 之外的平台没有ENOTCAPABLE:
CAP = getattr(errno, "ENOTCAPABLE", None) if CAP is not None and e.errno == CAP: print("capability 不足")场景四:EINTR 中断后的自动重试
EINTR(系统调用被信号中断)映射为InterruptedError。早期 Python 代码常需手动重试,如今多数系统调用会自动重启,但显式处理仍常见于信号密集的程序:
import errno import os while True: try: data = os.read(fd, 4096) break except InterruptedError: continue # errno == EINTR,重试系统调用用errorcode反向查名,输出可读日志
记录日志时把数值还原为符号名,便于排障阅读:
import errno def describe(e): code = getattr(e, "errno", None) if code is not None and code in errno.errorcode: return f"errno={code} ({errno.errorcode[code]}) {e.strerror}" return repr(e) try: 1 / len([]) except OSError as e: print(describe(e)) # 例如:errno=13 (EACCES) Permission deniederrno 模块的构建与运行机制(进阶)
从 Modules/errnomodule.c 可以进一步看到该模块在 CPython 内部的设计细节:
- 无方法、纯数据模块:模块方法表
errno_methods为空(见 errnomodule.c),全部内容都是初始化期写入的属性与字典; - 使用多阶段初始化(multi-phase init):通过
PyModuleDef_Slot声明Py_mod_exec钩子,真正填表工作放在errno_exec中完成(见 errnomodule.c),从而支持子解释器场景; - 自由线程(free-threaded)适配:slot 表中显式声明了
Py_mod_gil = Py_MOD_GIL_NOT_USED,表明该模块可在无 GIL 构建下安全运行;模块在Py_GIL_DISABLED构建下不强制Py_LIMITED_API(见 errnomodule.c); - 单条代码路径双向插入:
_add_errcode同时维护“模块符号属性”和“errorcode 字典”,从根上保证二者永不失配,配套测试 Lib/test/test_errno.py 进一步守护这一不变式。
总结
errno模块虽小,却是 CPython 打通“操作系统错误码 ↔ Python 层符号 ↔ OSError 异常子类”三方的关键枢纽:
- 符号⇄数值:
errno.EPERM与errno.errorcode[errno.EPERM]双向映射,随平台条件导出(见 Modules/errnomodule.c); - 数值→消息:交给
os.strerror(见 Doc/library/os.rst); - 数值→异常子类:由解释器内部
errnomap(见 Objects/exceptions.c)完成,让except FileNotFoundError、except PermissionError等写法成为可能。
理解这三层关系后,你写出的错误处理代码既能在 Linux/macOS/Windows/WASI 之间平滑移植,又能准确、可读地向调用方表达失败原因——这正是标准库把操作系统细节“翻译”给 Python 程序员的完整故事。
【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考