介绍
aiofiles 是 Python 生态最主流的异步磁盘文件库,Apache‑2.0 协议,专门给 asyncio 异步程序做本地文件读写。
原生IO
操作系统本身没有通用的异步磁盘文件API,Python原生open() 文件IO是 同步阻塞。直接在协程里调用内置open()会卡住事件循环,其他协程得不到调度,并发性能暴跌。
不是真正AIO
aiofiles 不是内核真正AIO,底层是把标准同步文件操作丢到线程池执行,封装成async/await协程接口,让事件循环不被磁盘IO阻塞。
提示:真正的异步IO是要调用操作系统API的,目前没有成熟的库,最流行的是 aiofiles
区分 aiofile
aiofiles ≠ aiofile。
aiofile是另一个库,可使用 Linux io_uring,调用 Linux 真正的异步API
aiofiles 只用线程池,跨平台兼容性最好。
安装
要求 Python ≥3.7,最新版本 25.x。
pip 安装
pip install aiofiles
uv 安装
uv add aiofiles
底层原理
- 调用
aiofiles.open()返回异步文件包装对象; - 所有IO方法(
read/write/seek/flush等)都是协程; - 内部通过
loop.run_in_executor(ThreadPoolExecutor, 同步文件函数),把真正的磁盘IO交给子线程执行; - 主线程事件循环继续跑其他协程;线程完成IO后把结果通过await交回协程。
本质
文件IO本身还是阻塞的,只是阻塞发生在线程池线程,不阻塞事件循环主线程。磁盘物理IO耗时不会消失,只是不拖累其他任务。
API说明
接口几乎复刻Python内置open,参数完全兼容:file, mode, buffering, encoding, errors, newline。
注意:文件对象所有IO方法必须加await;支持
async with上下文管理器,自动关闭文件;支持异步迭代按行读取。
常用方法
| 异步方法 | 说明 |
|---|---|
await f.read(size=-1) |
读取全部/指定字节/字符 |
await f.readline() |
读取一行 |
await f.readlines() |
读取全部行返回列表 |
await f.write(data) |
写入字符串/字节 |
await f.writelines(lines) |
批量写入多行 |
await f.seek(offset) |
文件指针跳转 |
await f.tell() |
获取当前指针位置 |
await f.flush() |
刷缓冲区到磁盘 |
async for line in f: |
异步逐行迭代,适合大文件 |
例子
1. 读取文本文件
import asyncio
import aiofiles
async def read_demo():
async with aiofiles.open("demo.txt", "r", encoding="utf‑8") as f:
content = await f.read()
print(content)
asyncio.run(read_demo())
2. 写入文件
async def write_demo():
async with aiofiles.open("out.txt", "w", encoding="utf‑8") as f:
await f.write("第一行\n")
await f.write("第二行\n")
asyncio.run(write_demo())
3. 追加日志 a模式
async def append_log():
async with aiofiles.open("app.log", "a", encoding="utf‑8") as f:
await f.write("2026‑08‑15 日志消息\n")
4. 大文件逐行读取(不要一次性read全部)
async def read_by_line():
async with aiofiles.open("big.txt", "r", encoding="utf‑8") as f:
async for line in f:
# 处理每一行
print(line.strip())
5. 二进制读写(下载保存文件场景)
async def save_bin():
async with aiofiles.open("test.bin", "wb") as f:
await f.write(b"binary data")
6. 临时文件支持
from aiofiles import tempfile
async def tmp_demo():
async with tempfile.TemporaryFile("wb+") as f:
await f.write(b"hello temp")
await f.seek(0)
data = await f.read()
print(data)
适用场景
- FastAPI / aiohttp异步web服务:读取静态资源、接收上传文件、写访问日志;协程中不能用普通open。
- 异步爬虫:大量并发下载,异步保存网页、图片二进制到本地。
- 异步任务队列:asyncio大量协程并发读写本地磁盘文件。
- 大文件分批处理,配合异步迭代。
重要限制与注意事项
- 不是真正内核异步IO:依赖线程池。当并发文件操作数量极大,线程池会耗尽,出现排队。可自定义executor传入
aiofiles.open调整线程池。 - 不适合极高吞吐磁盘IO:大量磁盘读写瓶颈在磁盘本身,
aiofiles无法加速磁盘硬件速度,只是不阻塞事件循环。 - 不要忘记
await:f.read()不加await得到协程对象,不是真实数据,也不会真正执行IO。 - 多协程同时写同一个文件依然会出现竞态,需要加锁
asyncio.Lock(),aiofiles本身不提供文件锁。 - 网络文件系统(NFS/SMB):线程池方案同样生效,但网络文件本身延迟高。
- 小文件场景收益有限;如果你的整个程序是同步代码,完全没必要引入aiofiles。
对比
| 方案 | 特点 |
|---|---|
内置open() |
同步阻塞,asyncio协程中会卡住事件循环,适合同步代码 |
| aiofiles | 线程池代理,接口贴近原生open,跨平台,生产最常用 |
| aiofile库 | 可选io_uring内核异步IO,Linux专用,接口有差异 |
最佳实践
- 异步程序中所有本地磁盘文件IO统一使用
aiofiles.open,避免原生open。 - 大文件不要用
await f.read()一次性全部读入内存,优先async for逐行或者分块读取。 - 多协程写同一个文件,必须加
asyncio.Lock互斥。 - 大量并发文件操作,如果线程池打满,可以自定义ThreadPoolExecutor传给loop。
- 使用
async with,不要手动调用await f.close(),避免文件泄漏。
常见坑
# ❌错误,没有await read,拿到协程对象
content = f.read()
# ❌错误,协程内使用原生open,阻塞事件循环
async def bad():
with open("a.txt") as f:
data = f.read()