不能直接用 open() 配合 async/await,因为它是阻塞式 io,会阻塞事件循环;aiofiles 通过线程池或 io_uring 实现真正异步文件操作,需用 async with 和 await 配合使用。

为什么不能直接用 open() 配合 async/await
因为标准库的 open() 是阻塞式 IO,哪怕包在 async def 里,也会让整个事件循环卡住。你看到 CPU 占用低、程序“假死”,大概率是这里堵住了。aiofiles 的作用就是把文件操作扔进线程池(默认)或用系统级异步接口(Linux 上可配 io_uring),让 await 真正生效。
aiofiles.open() 的基本用法和常见陷阱
它接口接近内置 open(),但返回的是一个异步上下文管理器,必须用 async with;直接 await aiofiles.open(...) 会报 TypeError: object AsyncContextManager can't be used in 'await' expression。
常见写法:
import aiofiles
<p>async def read_file():
async with aiofiles.open('data.txt', mode='r') as f:
content = await f.read() # 注意:这里要 await
return content</p><p>async def write_file():
async with aiofiles.open('out.txt', mode='w') as f:
await f.write('hello\n') # write 不自动换行
await f.flush() # 非必需,但确保内容落盘(尤其调试时)
</p>
- 模式字符串和内置
open()一致,支持'rb'、'a+'等 - 不支持
encoding参数传给open()—— 必须显式指定,如mode='r', encoding='utf-8' -
readlines()返回list[str],不是异步迭代器;大文件慎用,容易爆内存
读大文件时怎么避免 OOM?用 aiter() + async for
直接 await f.read() 会一次性加载全部内容。对几百 MB 的日志或二进制文件,这是危险操作。正确姿势是逐行或分块读取:
async def read_in_chunks():
async with aiofiles.open('huge.log', mode='r') as f:
async for line in f: # 自动按行迭代,底层用 aiter(f.__aiter__, b'')
process(line.strip())
<p>async def read_by_chunk():
async with aiofiles.open('data.bin', mode='rb') as f:
while True:
chunk = await f.read(8192) # 每次读 8KB
if not chunk:
break
process(chunk)
</p>
-
async for依赖文件对象实现了__aiter__,仅对文本模式有效;二进制模式得手动read(n) - chunk 大小不是越大越好:太小增加 syscall 次数,太大占内存。8KB–64KB 是较稳妥的起点
- 注意:Windows 上某些杀毒软件可能锁住正在写的文件,导致
async with aiofiles.open(..., 'w')报PermissionError
性能瓶颈往往不在 aiofiles 本身
aiofiles 默认使用 loop.run_in_executor() 调用线程池执行系统调用,这意味着它本质仍是同步 IO + 多线程调度。真正瓶颈常来自:
- 磁盘本身(尤其是机械硬盘随机读写)
- 频繁打开/关闭小文件(比批量处理慢一个数量级)
- 没做错误处理,比如
FileNotFoundError或权限问题导致协程崩溃 - 误以为“用了 async 就一定快”——单个文件顺序读写,纯同步
open()可能更快(无调度开销)
真正受益的场景是:并发读写多个文件、混合网络 IO 和文件 IO、或作为高吞吐 pipeline 的一环。单独压测 aiofiles 和 open(),结果可能让你意外。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











