python 3.12 的 exceptiongroup 对普通文件io错误无直接帮助,因其仅适用于并发场景中多个异常同时抛出;单次 open()/read() 仍抛单个异常,用传统 try/except 即可。

Python 3.12 的 ExceptionGroup 对文件IO错误没直接帮助
Python 3.12 引入了 ExceptionGroup 和 except*,但它们主要解决**并发任务中多个异常同时抛出**的场景(比如 asyncio.gather() 或 threading 批量操作),不改变单次 open() 或 read() 的错误捕获逻辑。普通文件IO仍抛出单个异常(如 FileNotFoundError、PermissionError),用传统 try/except 就够了。
真正该关注的是 os.open() + os.read() 场景下的错误分类
当绕过 open() 走底层系统调用时,Python 3.12 的 O_CLOEXEC 默认启用和更严格的 errno 映射会影响错误表现。比如:
-
os.open("missing.txt", os.O_RDONLY)抛FileNotFoundError(和以前一致) - 但
os.open("/proc/self/mem", os.O_RDONLY)在某些内核下可能抛PermissionError而非OSError,因为 Python 3.12 更精确地将errno.EACCES映射为PermissionError - 若需区分“不存在”和“没权限”,应显式捕获具体子类,而非只写
except OSError:
示例:
try:
fd = os.open("data.bin", os.O_RDONLY)
except FileNotFoundError:
print("文件根本不存在")
except PermissionError:
print("存在但无读取权限")
except OSError as e:
print(f"其他OS错误:{e}")
异步文件IO(asyncio.to_thread())才需要 except*
如果你用 asyncio.to_thread() 并发读多个文件,且其中部分失败,这时 ExceptionGroup 才有用。例如:
import asyncio
<p>async def read_file(path):
return await asyncio.to_thread(open, path).read()</p><p>async def main():
try:
results = await asyncio.gather(
read_file("a.txt"),
read_file("b.txt"), # 假设它不存在
read_file("c.txt"),
return_exceptions=True
)
except* FileNotFoundError as eg:
print(f"缺失 {len(eg.exceptions)} 个文件:{[type(e).<strong>name</strong> for e in eg.exceptions]}")
</p>
注意:return_exceptions=True 是关键,否则 gather() 直接抛 ExceptionGroup;except* 只匹配组内**全部**指定类型异常,不是“至少一个”。
别忽略 ResourceWarning 和 BlockingIOError
Python 3.12 对资源泄漏警告更敏感,尤其在未关闭的文件描述符上。另外,非阻塞IO(如 os.open(..., os.O_NONBLOCK))可能抛 BlockingIOError(继承自 OSError),它和 IOError 不同——后者在 3.12 中已彻底成为 OSError 的别名,不再独立存在。
- 检查是否漏关文件:用
with open(...)或确保fd被os.close() -
BlockingIOError需单独处理,不能靠except IOError:捕获(因为IOError已是别名,但语义不同) - 真实磁盘IO极少触发
BlockingIOError,多见于管道、socket 或/dev/stdin等特殊文件
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











