Python aiofiles 异步IO库

介绍

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

底层原理

  1. 调用aiofiles.open()返回异步文件包装对象;
  2. 所有IO方法(read/write/seek/flush等)都是协程;
  3. 内部通过 loop.run_in_executor(ThreadPoolExecutor, 同步文件函数),把真正的磁盘IO交给子线程执行;
  4. 主线程事件循环继续跑其他协程;线程完成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)

适用场景

  1. FastAPI / aiohttp异步web服务:读取静态资源、接收上传文件、写访问日志;协程中不能用普通open。
  2. 异步爬虫:大量并发下载,异步保存网页、图片二进制到本地。
  3. 异步任务队列:asyncio大量协程并发读写本地磁盘文件。
  4. 大文件分批处理,配合异步迭代。

重要限制与注意事项

  1. 不是真正内核异步IO:依赖线程池。当并发文件操作数量极大,线程池会耗尽,出现排队。可自定义executor传入aiofiles.open调整线程池。
  2. 不适合极高吞吐磁盘IO:大量磁盘读写瓶颈在磁盘本身,aiofiles 无法加速磁盘硬件速度,只是不阻塞事件循环。
  3. 不要忘记 await:f.read()不加 await 得到协程对象,不是真实数据,也不会真正执行IO。
  4. 多协程同时写同一个文件依然会出现竞态,需要加锁asyncio.Lock(),aiofiles本身不提供文件锁。
  5. 网络文件系统(NFS/SMB):线程池方案同样生效,但网络文件本身延迟高。
  6. 小文件场景收益有限;如果你的整个程序是同步代码,完全没必要引入aiofiles。

对比

方案 特点
内置open() 同步阻塞,asyncio协程中会卡住事件循环,适合同步代码
aiofiles 线程池代理,接口贴近原生open,跨平台,生产最常用
aiofile库 可选io_uring内核异步IO,Linux专用,接口有差异

最佳实践

  1. 异步程序中所有本地磁盘文件IO统一使用aiofiles.open,避免原生open。
  2. 大文件不要用await f.read()一次性全部读入内存,优先async for逐行或者分块读取。
  3. 多协程写同一个文件,必须加asyncio.Lock互斥。
  4. 大量并发文件操作,如果线程池打满,可以自定义ThreadPoolExecutor传给loop。
  5. 使用async with,不要手动调用await f.close(),避免文件泄漏。

常见坑

# ❌错误,没有await read,拿到协程对象
content = f.read()

# ❌错误,协程内使用原生open,阻塞事件循环
async def bad():
    with open("a.txt") as f:
        data = f.read()

原文出处:/show_1GW3rgpNcVpD.html