Python后端之异步读写文件的三种方式

3,888 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

  1. aiopathlib
from aiopathlib import AsyncPath # pip install aiopathlib

text = await AsyncPath('/path/to/file').read_text() # 读取文件内容
await AsyncPath('/path/to/file').write_text('content') # 写入文本
  1. anyio
from anyio import Path # pip install anyio

text = await Path('/path/to/file').read_text()
await Path('/path/to/file').write_text('content')
  1. aiofiles
import aiofiles # pip install aiofiles

async with aiofiles.open('/path/to/file') as fp:
    text = await fp.read()
    
async with aiofiles.open('/path/to/file', 'w') as fp:
    await fp.write('content')

补充说明,当使用异步框架,如FastAPI时,如果需要在接口里处理文件,使用标准库pathlib的话,由于是同步的,会产生IO阻塞,不能很好的利用异步框架的性能。改用异步await的方式,明显可以提高并发性能。

上述三种方式中,第一个是我编写的一个把aiofiles的语法封装成pathlib语法的简单库,已在生产环境使用超过半年时间,源代码在github.com/waketzheng/… 欢迎PR; 第二个是FastAPI的依赖库之一,主要用于让异步代码同时支持asyncio和trio,源代码在github.com/agronholm/a… 第三个aiofiles也是FastAPI的依赖库之一,它创建时间比anyio更早,只专注于文件的异步操作,源代码在github.com/Tinche/aiof…

附:一个使用AsyncPath的实际例子

from pathlib import Path
from aiopathlib import AsyncPath
BASE_DIR = Path(__file__).resolve().parent
DATA_ROOT = AsyncPath(BASE_DIR) / "datas"


async def build_zipfile(
    files: Tuple[bytes, bytes, bytes, bytes, bytes, bytes], mch_name: str
) -> AsyncPath:
    """files -> zipped local file"""
    names = ["21.png", "25.png", "26.png", "27.png", "28.pdf", "29.png"]
    lines = (
        '{"no":"%d","merchantType":"3","certNo":"%s","filePath":"/cert/3/%s"}'
        % (i, j.split(".")[0], j)
        for i, j in enumerate(names, 1)
    )
    txt = '{"key":"value"}\n' + "\n".join(lines)
    now = timezone.localtime()
    today = str(now.date())
    if not await (cert_path := DATA_ROOT / mch_name / today / "cert/3").exists():
        await cert_path.mkdir(parents=True)
    where = cert_path.parent.parent
    await (txt_path := where / "certInfo.txt").write_text(txt)
    certs = [cert_path.joinpath(n) for n in names]
    await asyncio.gather(*[i.write_bytes(j) for i, j in zip(certs, files)])
    zpath = where / f"{now:%Y%m%d%H%M%S}001.zip"
    with ZipFile(zpath, "w") as zp:
        zp.write(txt_path, str(txt_path).split(f"{today}/")[-1])
        for c in certs:
            zp.write(c, str(c).split(f"{today}/")[-1])
    return zpath

注:该函数用于读取多个本地文件,压缩成一个zip包