Ачааллж байна...
Өмнөх хичээлд бид ойлголтыг үзсэн: async нь хүлээлтийг ашигтай болгодог. Зөөгч нь гал тогоонд зогсохын оронд бусад ширээнд үйлчилдэг.
Одоо кодыг харна.
Гэхдээ зорилгоо дахин тодруулъя:
Зорилго нь ЭЗЭМШИХ биш — ТАНИХ.
Энэ хичээлийн төгсгөлд та async def гэсэн бичлэг харахад айхаа болино. Та түүнийг уншиж, "аан, энэ нь хүлээж байгаа хэсэг" гэж мэдэх болно.
Та өөрөө async код бичих ШААРДЛАГАГҮЙ. Хэрэв хэзээ ч бичихгүй бол — тэр ч бас зүгээр.
Гэхдээ уншиж чадах ёстой. Учир нь FastAPI-гийн documentation, жишээ код, бусдын төслүүд — бүгд async def -ээр дүүрэн байна.
Async кодыг ойлгохын тулд танд ГУРВАН шинэ зүйл л хэрэгтэй.
Түлхүүр үг | Утга |
|---|---|
| "Энэ функц нь ХҮЛЭЭЖ чадна" |
| "ЭНД хүлээж байна — өөр ажил хий" |
| "Async ертөнцийг ЭХЛҮҮЛ" |
Тэр л болоо. Гурван зүйл.
Тэдгээрийг нэг нэгээр нь үзье.
async def — "энэ функц хүлээж чадна"python
async def mendchilekh(ner: str) -> str:
return f"Сайн байна уу, {ner}!"Ердийн функцтэй ялгаа: def -ийн ӨМНӨ async.
Тэр л болоо. Өөр ямар ч ялгаа алга.
Гэвч тэр нэг үг нь БҮХ зүйлийг өөрчилдөг.
Туршиж үзье:
python
async def mendchilekh(ner: str) -> str:
return f"Сайн байна уу, {ner}!"
ur_dun = mendchilekh("Болд")
print(ur_dun)
print(type(ur_dun))<coroutine object mendchilekh at 0x000001A2B3C4D5E0>
<class 'coroutine'>ГЭНЭТИЙН ЗҮЙЛ! Функц нь string буцаагаагүй. Тэр нь coroutine объект буцаасан.
Танил санагдаж байна уу?
Бүлэг 13.1-ийн generator!
python
def kvadratuud(hemjee: int):
for i in range(hemjee):
yield i * i
gen = kvadratuud(5)
print(gen)<generator object kvadratuud at 0x...>ЯГ ИЖИЛ ЗАН ТӨЛӨВ.
Generator: функцийг дуудахад код АЖИЛЛАХГҮЙ. Generator объект үүснэ. Утга гуйхад л ажиллана.
Coroutine: функцийг дуудахад код АЖИЛЛАХГҮЙ. Coroutine объект үүснэ. Хэн нэгэн түүнийг АЖИЛЛУУЛАХ хүртэл.
Гол дүрэм:
async defфункцийг дуудахад код АЖИЛЛАХГҮЙ.Тэр нь зөвхөн "ажлын захиалга" үүсгэнэ.
Тэгвэл түүнийг ЯАЖ ажиллуулах вэ?
Хоёр арга бий: await эсвэл asyncio.run().
await — "энд хүлээж байна"python
import asyncio
async def mendchilekh(ner: str) -> str:
return f"Сайн байна уу, {ner}!"
async def main() -> None:
mesej = await mendchilekh("Болд") # AWAIT!
print(mesej)
asyncio.run(main())Сайн байна уу, Болд!Одоо ажиллалаа.
await нь ХОЁР зүйл хийдэг:
1. Coroutine -ыг АЖИЛЛУУЛНА — түүний үр дүнг авна.
2. "ЭНД ХҮЛЭЭЖ БАЙНА, ӨӨР АЖИЛ ХИЙ" гэж хэлнэ.
Хоёр дахь нь илүү чухал.
await бол ЗӨӨГЧИЙН СОЛИГДОХ ЦЭГ.
Тэр мөрөнд хүрэхэд зөөгч (Python) нь ингэж бодно:
"Аан, энд хүлээх ажил байна. Би зогсоод хүлээхгүй. Би ӨӨР ажил руу шилжинэ. Хариу ирэхэд буцаж ирнэ."
await байхгүй газарт солигдолт БОЛОХГҮЙ.
await-ыг ЗӨВХӨНasync defфункц ДОТОР бичиж болно.
Ердийн функц дотор бичвэл:
python
def main() -> None: # ердийн def!
mesej = await mendchilekh("Болд")
print(mesej) File "main.py", line 2
mesej = await mendchilekh("Болд")
^^^^^
SyntaxError: 'await' outside async functionSyntaxError: 'await' outside async function — "await нь async функцээс гадна байна."
Энэ бол ХАМГИЙН түгээмэл async алдаа.
Учир шалтгаан нь энгийн: зөвхөн async def функц нь ЗОГСООД, дараа нь ҮРГЭЛЖЛЭХ чадвартай. Ердийн функц тэгж чадахгүй.
Бүлэг 13.1-ийн yield -тэй ижил санаа:
"
yieldнь функцийг ЗОГСООДОГ, гэхдээ УСТГАДАГГҮЙ. Төлөв нь хэвээрээ."
await нь яг тэр зүйлийг хийдэг — зөвхөн "хүлээж байна" гэсэн утгаар.
Одоо Хичээл 14.1-т дурдсан асуудал бодит болов.
await бичихийн тулд → функц нь async def байх ёстой.
Тэр функцийг дуудахын тулд → await бичих хэрэгтэй.
Тэр дуудагч ч бас async def байх ёстой.
Тэр нь ДЭЭШЭЭ, ДЭЭШЭЭ тархана:
python
async def uguudul_avakh(): # async
return await api_duudakh() # await
async def boloviruulakh(): # async БАЙХ ЁСТОЙ
uguudul = await uguudul_avakh() # await
return uguudul
async def main(): # async БАЙХ ЁСТОЙ
ur_dun = await boloviruulakh() # await
print(ur_dun)
asyncio.run(main()) # ЭНД л ердийн ертөнц эхэлнэГинж бүхэлдээ async.
Тэр нь программистуудын "function coloring problem" (функцийн өнгө) гэж нэрлэдэг зүйл юм: async функцууд нь "өөр өнгөтэй" бөгөөд тэдгээр нь бүх гинжийг будаж эхэлдэг.
Тэр бол async -ийн жинхэнэ үнэ төлбөр.
asyncio.run() — ертөнцийг эхлүүлэхОдоо асуулт: хамгийн дээд түвшинд юу байх вэ?
main() нь async def байна. Гэвч түүнийг ХЭН дуудах вэ? Модулийн дээд түвшинд await бичиж болохгүй (тэр нь функц биш).
Хариулт: asyncio.run().
python
import asyncio
async def main() -> None:
...
asyncio.run(main())asyncio.run() нь:
EVENT LOOP үүсгэнэ (async ертөнцийн "зөөгч")
main() coroutine -ыг ажиллуулна
Бүх ажил дуустал хүлээнэ
Event loop -ыг хаана
asyncio.run() бол ЕРДИЙН функц. Түүнийг ердийн кодоос дуудаж болно.
Тэр бол ХИЛИЙН ЦЭГ: түүнээс дээш — ердийн ертөнц. Түүнээс доош — async ертөнц.
Event loop гэж юу вэ?
Тэр бол ЗӨӨГЧ.
Тэр нь:
Ямар ажлууд байгааг мэднэ
Аль нь ХҮЛЭЭЖ байгааг мэднэ
Аль нь БЭЛЭН болсныг мэднэ
Тэдгээрийн хооронд СОЛИГДОНО
Тэр нь БҮХ async ажлыг зохицуулж байна.
Энэ курс нь event loop -ийн нарийвчлалыг үзэхгүй. Зөвхөн "тэр байдаг, тэр нь зохицуулагч" гэдгийг мэдэхэд хангалттай.
Одоо async -ийн ЖИНХЭНЭ ажлыг харъя. asyncio.sleep() -ыг ашиглан хүлээлтийг дуурайлгая.
Эхлээд СИНХРОН (танил):
python
import time
def ajil(ner: str, khugatsaa: float) -> str:
print(f" {ner}: эхлэв")
time.sleep(khugatsaa) # ХҮЛЭЭЖ байна
print(f" {ner}: дууслаа")
return f"{ner} бэлэн"
ekhelsen = time.time()
a = ajil("Ажил A", 1)
b = ajil("Ажил B", 1)
c = ajil("Ажил C", 1)
duussan = time.time()
print(f"\nНийт: {duussan - ekhelsen:.2f} секунд") Ажил A: эхлэв
Ажил A: дууслаа
Ажил B: эхлэв
Ажил B: дууслаа
Ажил C: эхлэв
Ажил C: дууслаа
Нийт: 3.01 секундГурван ажил, ДАРААЛЛААР. 3 секунд.
Анзаараарай: A дуусахгүйгээр B эхлээгүй.
Одоо АСИНХРОН:
python
import asyncio
import time
async def ajil(ner: str, khugatsaa: float) -> str:
print(f" {ner}: эхлэв")
await asyncio.sleep(khugatsaa) # AWAIT!
print(f" {ner}: дууслаа")
return f"{ner} бэлэн"
async def main() -> None:
ekhelsen = time.time()
ur_dun = await asyncio.gather( # ЗЭРЭГ ажиллуул!
ajil("Ажил A", 1),
ajil("Ажил B", 1),
ajil("Ажил C", 1),
)
duussan = time.time()
print(f"\nҮр дүн: {ur_dun}")
print(f"Нийт: {duussan - ekhelsen:.2f} секунд")
asyncio.run(main()) Ажил A: эхлэв
Ажил B: эхлэв
Ажил C: эхлэв
Ажил A: дууслаа
Ажил B: дууслаа
Ажил C: дууслаа
Үр дүн: ['Ажил A бэлэн', 'Ажил B бэлэн', 'Ажил C бэлэн']
Нийт: 1.00 секунд3 СЕКУНД → 1 СЕКУНД.
ГАРАЛТЫГ АНХААРАЛТАЙ ХАРААРАЙ. Тэр нь бүх зүйлийг тайлбарлаж байна:
Ажил A: эхлэв ← A эхлэв, await -д хүрэв
Ажил B: эхлэв ← зөөгч B рүү шилжив!
Ажил C: эхлэв ← зөөгч C рүү шилжив!
Ажил A: дууслаа ← A -гийн хүлээлт дуусав
Ажил B: дууслаа
Ажил C: дууслааГурвуулаа ЭХЛЭЭД эхэлсэн, дараа нь гурвуулаа дууссан.
Хүлээлтүүд ДАВХЦСАН.
asyncio.sleep() vs time.sleep()Одоо ЧУХАЛ ялгааг тодруулъя.
time.sleep(1) — БЛОКЛОДОГ. Бүх зүйлийг зогсооно. Зөөгч гал тогоонд зогсоно.
asyncio.sleep(1) — БЛОКЛОДОГГҮЙ. "Би нэг секунд хүлээнэ, тэр хугацаанд өөр ажил хий."
Хэрэв та async функц дотор time.sleep() бичвэл:
python
async def ajil(ner: str, khugatsaa: float) -> str:
print(f" {ner}: эхлэв")
time.sleep(khugatsaa) # БУРУУ! БЛОКЛОНО!
print(f" {ner}: дууслаа")
return f"{ner} бэлэн" Ажил A: эхлэв
Ажил A: дууслаа
Ажил B: эхлэв
Ажил B: дууслаа
Ажил C: эхлэв
Ажил C: дууслаа
Нийт: 3.01 секундAsync нь АЖИЛЛААГҮЙ. 3 секунд хэвээрээ.
Учир нь time.sleep() нь БҮХ ЗҮЙЛИЙГ блоклосон. Зөөгч солигдож чадаагүй.
Энэ бол ХАМГИЙН НУУЦ, ХАМГИЙН АЮУЛТАЙ async алдаа. Код нь ажиллана, алдаа гарахгүй — гэхдээ async нь утгагүй болно.
Дүрэм: async функц дотор БЛОКЛОДОГ зүйл бичиж БОЛОХГҮЙ.
Блоклодог зүйлс:
time.sleep() → asyncio.sleep() ашигла
requests.get() → httpx эсвэл aiohttp ашигла
Ердийн файл унших → aiofiles ашигла
Том тооцоолол → async нь тусалахгүй (Хичээл 14.1)
Яг тиймээс Хичээл 14.1-т би "requests нь async -ыг ДЭМЖДЭГГҮЙ" гэж хэлсэн.
asyncio.gather() — зэрэг ажиллуулахДээрх кодод шинэ зүйл гарч ирлээ:
python
ur_dun = await asyncio.gather(
ajil("Ажил A", 1),
ajil("Ажил B", 1),
ajil("Ажил C", 1),
)asyncio.gather() нь: "эдгээр БҮХ coroutine -ыг ЗЭРЭГ ажиллуул, бүгд дуусахыг хүлээ."
Анзаараарай: ajil(...) -ыг await -гүйгээр дуудаж байна.
Учир нь async def функцийг дуудахад — тэр нь coroutine объект үүсгэнэ (ажиллахгүй). Тэдгээр гурван объектыг gather -т өгч байна.
Дараа нь await asyncio.gather(...) нь бүгдийг зэрэг ажиллуулна.
Хэрэв gather -гүйгээр бичвэл:
python
async def main() -> None:
a = await ajil("Ажил A", 1) # хүлээв
b = await ajil("Ажил B", 1) # дараа нь хүлээв
c = await ajil("Ажил C", 1) # дараа нь хүлээвНийт: 3.01 секундДАХИН 3 СЕКУНД!
Учир нь await бүр нь ХҮЛЭЭДЭГ. A дуусахгүйгээр B эхлэхгүй.
Гол дүрэм:
awaitдараалан бичих ≠ зэрэг ажиллах.Зэрэг ажиллуулахын тулд
asyncio.gather()хэрэгтэй.
Тэр нь маш түгээмэл эндүүрэл.
Одоо Хичээл 14.1-ийн жишээг async -ээр бичье.
requests нь ажиллахгүй тул httpx -ыг ашиглана:
powershell
pip install httpxpython
import asyncio
import time
import httpx
KHOTUUD = [
(47.92, 106.92, "Улаанбаатар"),
(49.49, 105.92, "Дархан"),
(49.03, 104.08, "Эрдэнэт"),
(35.68, 139.69, "Токио"),
(51.51, -0.13, "Лондон"),
]
URL = "https://api.open-meteo.com/v1/forecast"
async def tsag_agaar_avakh(
client: httpx.AsyncClient, lat: float, lon: float, ner: str
) -> tuple[str, float]:
parametruud = {
"latitude": lat,
"longitude": lon,
"current": "temperature_2m",
"timezone": "auto",
}
hariult = await client.get(URL, params=parametruud, timeout=10)
hariult.raise_for_status()
temp = hariult.json()["current"]["temperature_2m"]
return (ner, temp)
async def main() -> None:
ekhelsen = time.time()
async with httpx.AsyncClient() as client:
ur_dun = await asyncio.gather(
*[
tsag_agaar_avakh(client, lat, lon, ner)
for lat, lon, ner in KHOTUUD
]
)
duussan = time.time()
print("=" * 40)
for ner, temp in ur_dun:
print(f"{ner:<15} {temp:>6.1f}°C")
print("=" * 40)
print(f"Нийт хугацаа: {duussan - ekhelsen:.2f} секунд")
asyncio.run(main())========================================
Улаанбаатар 18.7°C
Дархан 17.2°C
Эрдэнэт 16.9°C
Токио 28.4°C
Лондон 21.3°C
========================================
Нийт хугацаа: 0.58 секунд2.47 СЕКУНД → 0.58 СЕКУНД. ДӨРӨВ ДАХИН хурдан.
Кодыг задалъя. Танил зүйлс маш олон байна:
async with httpx.AsyncClient() as client: — async with!
Бүлэг 13.3-ын with нь async хувилбартай. Тэр нь __aenter__ ба __aexit__ ашигладаг (async context manager).
Ижил санаа: цэвэрлэгээг баталгаажуулна.
await client.get(...) — requests.get() -тэй бараг ижил, зөвхөн await -тэй.
*[...] — Бүлэг 3.3-ын ОД! Comprehension -ээс жагсаалт үүсгээд, түүнийг gather -т ЗАДЛАЖ өгч байна.
gather(*[a, b, c]) = gather(a, b, c).
-> tuple[str, float] — Бүлэг 5.1-ийн tuple hint.
Comprehension — Бүлэг 2.1.
Хуучин мэдлэг бүгд ажиллаж байна. Шинэ зүйл нь зөвхөн async, await, gather.
Одоо хамгийн чухал ур чадвар: async кодыг унших.
Хэрэв та ийм код харвал:
python
@app.get("/nomnuud")
async def nomnuud_jagsaakh() -> list[Nom]:
nomnuud = await db.fetch_all("SELECT * FROM nomnuud")
return nomnuudИнгэж уншаарай:
async def → "энэ функц нь хүлээж чадна"
await db.fetch_all(...) → "ЭНД өгөгдлийн сангийн хариу хүлээж байна. Тэр хугацаанд сервер нь ӨӨР хэрэглэгчид үйлчилж байна."
Тэр л болоо.
Хэрэв та async ба await -ыг ЗАЙЛСХИЙВЭЛ — код нь ердийн синхрон код болно:
python
@app.get("/nomnuud")
def nomnuud_jagsaakh() -> list[Nom]:
nomnuud = db.fetch_all("SELECT * FROM nomnuud")
return nomnuudЛогик нь ЯГ ИЖИЛ. Зөвхөн хүлээх зан төлөв өөр.
Тиймээс async кодыг уншихдаа:
async ба await -ыг УУРАН ЗАЙЛСХИЙ
Үлдсэн кодыг ердийн Python шиг унш
Дараа нь await бүр дээр "энэ нь хүлээх цэг" гэж тэмдэглэ
Ихэнх тохиолдолд тэр л хангалттай.
Хэрэв сонирхвол: дээрх asyncio.sleep() -ийн жишээг өөрөө ажиллуулж, гаралтын ДАРААЛЛЫГ анхааралтай хараарай. "Эхлэв, эхлэв, эхлэв, дууслаа, дууслаа, дууслаа" — тэр нь async -ийн бүх утга учрыг харуулна.
Дараа нь asyncio.sleep() -ыг time.sleep() болгож солиод, async нь АЖИЛЛАХАА БОЛИХЫГ өөрийн нүдээр хараарай.
Тэр туршилт нь "блоклодог зүйл бичиж болохгүй" гэсэн дүрмийг хэзээ ч мартахгүй болгоно.
Мөн хүсвэл: asyncio.gather() -ыг АВЧ хаяад, await -үүдийг дараалан бичээд, хугацаа 3 секунд болохыг хараарай.
1. await -ыг ердийн функц дотор бичих
SyntaxError: 'await' outside async functionawait нь ЗӨВХӨН async def дотор.
2. await -ыг мартах
python
async def main() -> None:
mesej = mendchilekh("Болд") # await БАЙХГҮЙ!
print(mesej)<coroutine object mendchilekh at 0x...>
RuntimeWarning: coroutine 'mendchilekh' was never awaitedcoroutine ... was never awaited — "coroutine нь хэзээ ч await хийгдээгүй."
Функц нь ОГТ АЖИЛЛААГҮЙ. await бич.
3. time.sleep() -ыг async дотор ашиглах
Алдаа гарахгүй. Гэвч async нь утгагүй болно. asyncio.sleep() ашигла.
4. requests -ыг async дотор ашиглах
Ижил асуудал. БЛОКЛОНО. httpx эсвэл aiohttp ашигла.
5. await дараалан бичээд "зэрэг ажиллана" гэж бодох
python
a = await ajil1()
b = await ajil2() # a дуусахыг хүлээв!Дараалан ажиллана. Зэрэг хэрэгтэй бол asyncio.gather().
6. asyncio.run() -ыг мартах
python
async def main() -> None:
print("Сайн байна уу")
main() # coroutine үүсгээд хаявRuntimeWarning: coroutine 'main' was never awaitedЮу ч хэвлэгдэхгүй. asyncio.run(main()) бич.
7. asyncio.run() -ыг async функц дотор дуудах
python
async def main() -> None:
asyncio.run(dotood()) # БУРУУ!RuntimeError: asyncio.run() cannot be called from a running event loopasyncio.run() нь ЗӨВХӨН хамгийн ДЭЭД түвшинд. Дотор нь await ашигла.
Гурван шинэ зүйл л хэрэгтэй:
async def — "энэ функц ХҮЛЭЭЖ чадна"
await — "ЭНД хүлээж байна, өөр ажил хий"
asyncio.run() — "async ертөнцийг ЭХЛҮҮЛ"
async def функцийг дуудахад код АЖИЛЛАХГҮЙ. Тэр нь coroutine объект үүсгэнэ — generator -той ЯГ ИЖИЛ зан төлөв (Бүлэг 13.1).
await нь ХОЁР зүйл хийдэг: coroutine -ыг ажиллуулна, БАС "энд солигдож болно" гэж хэлнэ.
await -ыг ЗӨВХӨН async def дотор бичиж болно → эс тэгвээс SyntaxError: 'await' outside async function.
"Function coloring problem": await бичихийн тулд функц нь async байх ёстой → түүний дуудагч ч async → гинж бүхэлдээ async болно. Тэр бол async -ийн ЖИНХЭНЭ үнэ төлбөр.
asyncio.run() нь EVENT LOOP (зөөгч) үүсгэж, ажиллуулна. Тэр бол ХИЛИЙН ЦЭГ: дээш нь ердийн ертөнц, доош нь async.
asyncio.sleep() ≠ time.sleep().
time.sleep() — БЛОКЛОНО. Async нь утгагүй болно.
asyncio.sleep() — БЛОКЛОХГҮЙ. Зөөгч солигдоно.
Async функц дотор БЛОКЛОДОГ зүйл бичиж БОЛОХГҮЙ: time.sleep(), requests.get() (→ httpx), ердийн файл унших.
asyncio.gather() — олон coroutine -ыг ЗЭРЭГ ажиллуулна.
await дараалан бичих ≠ зэрэг ажиллах! Дараалан бичвэл — дараалан хүлээнэ.
Бодит үр дүн: таван хот, 2.47 секунд → 0.58 секунд.
async with — Бүлэг 13.3-ын with -ийн async хувилбар.
АСYNC КОДЫГ УНШИХ АРГА:
async ба await -ыг уураар зайлсхий
Үлдсэнийг ердийн Python шиг унш
await бүр дээр "энэ нь хүлээх цэг" гэж тэмдэглэ
Дараагийн хичээлээр Бүлэг 14 дуусна. Тэр нь маш богино байх бөгөөд ганцхан асуултад ШУДАРГА хариулт өгнө:
"Async надад ХЭРЭГТЭЙ юу?"
Хариулт нь таныг гайхшруулж болно. Мөн тэнд би FastAPI-гийн албан ёсны байр суурийг хэлнэ: async def ба def — хоёулаа ЗӨВ, аль нэгийг сонгох нь ТАНЫ шийдвэр.
Тэр хичээл нь async -ийн айдсыг бүрмөсөн арилгах болно.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.