کار با فایل ها به صورت ناهمزمان در پایتون با استفاده از aiofile و asyncio

کدهای ناهمزمان به پایه اصلی توسعه پایتون تبدیل شده است. با تبدیل شدن asyncio به بخشی از کتابخانه استاندارد و بسیاری از بسته های شخص ثالث که ویژگی های سازگار با آن را ارائه می دهند، این پارادایم به این زودی ها از بین نمی رود.
اگر در حال نوشتن کد ناهمزمان هستید، مهم است که مطمئن شوید همه بخشهای کدتان با هم کار میکنند، بنابراین یکی از جنبههای آن باعث کاهش سرعت سایر موارد نمیشود. فایل ورودی/خروجی میتواند یک مسدودکننده رایج در این زمینه باشد، بنابراین اجازه دهید نحوه استفاده از کتابخانه aiofiles برای کار با فایلها به صورت ناهمزمان را بررسی کنیم.
با شروع، این همه کدی است که برای خواندن محتویات یک فایل به صورت ناهمزمان (در یک تابع async) نیاز دارید:
async with aiofiles.open('filename', mode='r') as f:
contents = await f.read()
print(contents)
بیایید به جلو برویم و عمیق تر کاوش کنیم.
کد غیر مسدود کننده چیست؟
ممکن است عباراتی مانند “ناهمزمان”، “غیر مسدود کننده” یا “همزمان” را بشنوید و در مورد معنای همه آنها کمی گیج شوید. طبق این آموزش بسیار دقیق تر، دو ویژگی اصلی عبارتند از:
- روالهای ناهمزمان میتوانند در حالی که منتظر نتیجه نهایی خود هستند، «مکث» کنند تا به روالهای دیگر در این فاصله اجرا شوند.
-
کد ناهمزمان، از طریق مکانیسم بالا، اجرای همزمان را تسهیل می کند. به بیان دیگر، کد ناهمزمان ظاهر و حس همزمانی را می دهد.
بنابراین کد ناهمزمان کدی است که میتواند در حین انتظار برای نتیجه معلق بماند تا در این فاصله کدهای دیگر اجرا شوند. اجرای کدهای دیگر را مسدود نمیکند، بنابراین میتوانیم آن را کد «غیر مسدودکننده» بنامیم.
کتابخانه asyncio ابزارهای مختلفی را در اختیار توسعه دهندگان پایتون قرار می دهد تا این کار را انجام دهند و aiofiles حتی عملکرد خاص تری را برای کار با فایل ها ارائه می دهد.
راه اندازی
قبل از شروع، مطمئن شوید که محیط پایتون خود را تنظیم کرده اید. در صورت نیاز به کمک، این راهنما را از طریق بخش virtualenv دنبال کنید. اگر چندین پروژه در یک دستگاه اجرا می شود، انجام درست کار کردن همه چیز، به خصوص در مورد محیط های مجازی، برای جداسازی وابستگی های شما مهم است. برای اجرای کد در این پست حداقل به پایتون 3.7 یا بالاتر نیاز دارید.
اکنون که محیط شما راه اندازی شده است، باید چند کتابخانه شخص ثالث را نصب کنید. ما قصد داریم از aiofile ها استفاده کنیم، بنابراین پس از فعال کردن محیط مجازی خود، آن را با دستور زیر نصب کنید:
pip install aiofiles==0.6.0
برای مثالهای موجود در ادامه این پست، از فایلهای JSON دادههای API Pokemon مطابق با 150 Pokemon اصلی استفاده خواهیم کرد. میتوانید یک پوشه با همه آنها را از اینجا دانلود کنید. با این کار باید آماده حرکت و نوشتن کدی باشید.
خواندن از یک فایل با aiofile
بیایید با باز کردن یک فایل مربوط به یک Pokemon خاص، تجزیه JSON آن در یک فرهنگ لغت و چاپ نام آن شروع کنیم:
import aiofiles
import asyncio
import json
async def main():
async with aiofiles.open('articuno.json', mode='r') as f:
contents = await f.read()
pokemon = json.loads(contents)
print(pokemon['name'])
asyncio.run(main())
هنگام اجرای این کد، باید “articuno” را در ترمینال چاپ شده ببینید. همچنین می توانید خط به خط فایل را به صورت ناهمزمان تکرار کنید (این کد تمام 9271 خط را چاپ می کند. articuno.json
):
import aiofiles
import asyncio
async def main():
async with aiofiles.open('articuno.json', mode='r') as f:
async for line in f:
print(line)
asyncio.run(main())
نوشتن روی فایل با aiofile
نوشتن روی یک فایل نیز مشابه فایل ورودی/خروجی استاندارد پایتون است. فرض کنید میخواستیم فایلهایی حاوی فهرستی از تمام حرکاتی که هر پوکمون میتواند یاد بگیرد ایجاد کنیم. برای مثال ساده، در اینجا کاری است که ما برای Pokemon Ditto انجام می دهیم، که فقط می تواند حرکت “تبدیل” را یاد بگیرد:
import aiofiles
import asyncio
async def main():
async with aiofiles.open('ditto_moves.txt', mode='w') as f:
await f.write('transform')
asyncio.run(main())
بیایید این را با یک پوکمون که بیش از یک حرکت دارد، مانند ریدون، امتحان کنیم:
import aiofiles
import asyncio
import json
async def main():
# Read the contents of the json file.
async with aiofiles.open('rhydon.json', mode='r') as f:
contents = await f.read()
# Load it into a dictionary and create a list of moves.
pokemon = json.loads(contents)
name = pokemon['name']
moves = [move['move']['name'] for move in pokemon['moves']]
# Open a new file to write the list of moves into.
async with aiofiles.open(f'{name}_moves.txt', mode='w') as f:
await f.write('\n'.join(moves))
asyncio.run(main())
اگر باز کنی rhydon_moves.txt
شما باید فایلی با 112 خط ببینید که چیزی شبیه به این شروع می شود.
استفاده از asyncio برای عبور از بسیاری از فایل ها به صورت ناهمزمان
حالا بیایید کمی پیچیده تر شویم و این کار را برای تمام 150 پوکمون که فایل های JSON برای آنها داریم انجام دهیم. کد ما باید از هر فایل بخواند، JSON را تجزیه کند، و حرکات هر پوکمون را در یک فایل جدید بازنویسی کند:
import aiofiles
import asyncio
import json
from pathlib import Path
directory = 'directory/your/files/are/in'
async def main():
pathlist = Path(directory).glob('*.json')
# Iterate through all json files in the directory.
for path in pathlist:
# Read the contents of the json file.
async with aiofiles.open(f'{directory}/{path.name}', mode='r') as f:
contents = await f.read()
# Load it into a dictionary and create a list of moves.
pokemon = json.loads(contents)
name = pokemon['name']
moves = [move['move']['name'] for move in pokemon['moves']]
# Open a new file to write the list of moves into.
async with aiofiles.open(f'{directory}/{name}_moves.txt', mode='w') as f:
await f.write('\n'.join(moves))
asyncio.run(main())
پس از اجرای این کد، باید دایرکتوری فایل های پوکمون را مشاهده کنید .txt
فایل ها در کنار .json
آنهایی که حاوی لیست حرکت مربوط به هر پوکمون هستند.
اگر نیاز به انجام برخی اقدامات ناهمزمان دارید و میخواهید با دادههای مربوط به آن وظایف ناهمزمان، مانند فهرستی با حرکات هر پوکمون پس از نوشتن فایلها، پایان دهید، میتوانید از آن استفاده کنید. asyncio.ensure_future
و asyncio.gather
.
شما میتوانید بخشی از کد خود را که هر فایل را مدیریت میکند به تابع همگام خود تقسیم کنید و وعدههایی را برای آن فراخوانی تابع به لیستی از وظایف اضافه کنید. در اینجا نمونه ای از عملکرد آن و عملکرد جدید شما آورده شده است main
تابع به نظر می رسد:
async def write_pokemon_moves(filename):
# Read the contents of the json file.
async with aiofiles.open(f'{directory}/{filename}', mode='r') as f:
contents = await f.read()
# Load it into a dictionary and create a list of moves.
pokemon = json.loads(contents)
name = pokemon['name']
moves = [move['move']['name'] for move in pokemon['moves']]
# Open a new file to write the list of moves into.
async with aiofiles.open(f'{directory}/{name}_moves.txt', mode='w') as f:
await f.write('\n'.join(moves))
return { 'name': name, 'moves': moves }
async def main():
pathlist = Path(directory).glob('*.json')
# A list to be populated with async tasks.
tasks = []
# Iterate through all json files in the directory.
for path in pathlist:
tasks.append(asyncio.ensure_future(write_pokemon_moves(path.name)))
# Will contain a list of dictionaries containing Pokemons' names and moves
moves_list = await asyncio.gather(*tasks)
این یک روش رایج برای استفاده از کدهای ناهمزمان در پایتون است و اغلب برای مواردی مانند درخواست HTTP استفاده می شود.
پس من از این برای چه استفاده کنم؟
مثالهای موجود در این پست با استفاده از دادههای Pokemon فقط بهانهای برای نشان دادن عملکرد ماژول aiofiles و نحوه نوشتن کد برای پیمایش در فهرست فایلها برای خواندن و نوشتن بود. امیدواریم که بتوانید این نمونه کدها را با مشکلات خاصی که میخواهید حل کنید تطبیق دهید تا فایل ورودی/خروجی به یک مسدودکننده در کد ناهمزمان شما تبدیل نشود.
ما فقط سطح کارهایی را که میتوانید با aiohttp و asyncio انجام دهید خراشیدهایم، اما امیدوارم که این شروع سفر شما به دنیای پایتون ناهمزمان را کمی آسانتر کرده باشد.
من مشتاقانه منتظرم ببینم شما چه می سازید. با خیال راحت تماس بگیرید و تجربیات خود را به اشتراک بگذارید یا هر سوالی بپرسید.