Ачааллж байна...
Та энэ сургалтад олон зуун endpoint бичсэн. Бүгд ижил хэлбэртэй байсан:
python
@app.get("/notes")
def get_notes():
...Ердийн def. Гэхдээ та интернэтээс FastAPI-ийн жишээ хайхад ихэнх нь ийм харагддаг:
python
@app.get("/notes")
async def get_notes():
...async def. Тэр нэмэлт үг хаанаас ирсэн бэ? Бид яагаад ашиглаагүй вэ? Буруу зүйл хийсэн үү?
Энэ хичээл тэр асуултад хариулна. Богино хариу нь: үгүй, бид буруу зүйл хийгээгүй. Урт хариу нь энэ хичээлийн агуулга юм.
Курс 2-ын Бүлэг 14-т та async-ыг уншиж таних түвшинд сурсан. Тэр үед би "мастер болох шаардлагагүй, таних хангалттай — FastAPI-д тэр нь ойлгомжтой болно" гэж хэлсэн. Одоо тэр амлалт биелэх цаг иржээ.
Курс 2-ын Бүлэг 14-ийн үндсэн санааг сэргээе.
Программ ажиллахдаа ихэвчлэн хүлээдэг. Файл уншихыг хүлээнэ. Сүлжээний хариуг хүлээнэ. Өгөгдлийн сангаас хариу ирэхийг хүлээнэ.
Тэр хүлээлтийн үед процессор чөлөөтэй байдаг. Тэр юу ч хийхгүй суудаг.
Синхрон (ердийн) кодонд энэ нь зүгээр л алдагдсан цаг. Программ хүлээж зогсоно, дараа нь үргэлжилнэ.
Асинхрон кодонд программ хүлээж байх зуураа өөр ажил хийж чадна. Хариу ирэхэд буцаж ирж, үргэлжилнэ.
Курс 2-ын зүйрлэл: цайны газрын зөөгч. Муу зөөгч нэг ширээний хоол бэлдэгдэх хүртэл гал тогооны хажууд зогсоно. Сайн зөөгч тэр зуураа өөр ширээнд үйлчилнэ, хоол бэлэн болоход буцаж ирнэ.
async def болон await бол Python-д тэр "сайн зөөгч"-ийн зан төлөвийг бичих арга юм.
Одоо хамгийн чухал зүйлийг хэлье, учир нь энэ нь олон хүнийг гайхшруулдаг.
FastAPI нь def болон async def хоёуланг дэмждэг. Хоёулаа бүрэн ажиллана. Аль нэг нь "зөв", нөгөө нь "буруу" биш.
Гэхдээ тэдгээр нь өөр өөр аргаар ажилладаг. Ялгааг ойлгох нь чухал.
async def — үндсэн урсгалд
python
@app.get("/notes")
async def get_notes():
...Энэ функц нь FastAPI-ийн үндсэн урсгалд (main event loop) шууд ажиллана. Хамгийн хурдан, хамгийн үр ашигтай.
Гэхдээ болгоомжтой байх ёстой. Хэрэв энэ функц дотор удаан, синхрон ажил хийвэл (жишээ нь том файл унших, эсвэл time.sleep) — тэр нь бүх серверийг блоклоно. Бусад хүсэлтүүд хүлээх болно.
Учир нь үндсэн урсгал ганц. Хэрэв түүнийг эзэлбэл өөр юу ч ажиллахгүй.
def — тусдаа урсгалд
python
@app.get("/notes")
def get_notes():
...Энэ функцийг FastAPI тусдаа урсгалд (thread pool) явуулна. Үндсэн урсгал чөлөөтэй үлдэнэ.
Тиймээс хэрэв энэ функц удаан ажилласан ч бусад хүсэлтэд нөлөөлөхгүй. Сервер үргэлжлүүлэн ажиллана.
Энэ нь бага зэрэг илүү нөөц зарцуулдаг (урсгал үүсгэх), гэхдээ аюулгүй.
Гол дүгнэлт
FastAPI нь def функцийг ухаалгаар зохицуулдаг. Та async мэдэхгүй ч гэсэн зөв, найдвартай API бичиж чадна.
Тиймээс бид энэ сургалтад def ашигласан нь буруу биш, харин зориудаар сонгосон шийдвэр байсан. Async-ыг сурахаас өмнө API бүтээх ойлголтуудад анхаарлаа төвлөрүүлэх нь илүү зөв.
Одоо хамгийн ашигтай хэсэг. Хэзээ аль нэгийг сонгох вэ?
Дүрэм: await хэрэглэх зүйл байхгүй бол def ашигла.
Өөрөөр хэлбэл:
Хэрэв таны функц дотор await гэсэн үг байхгүй бол def бич.
Хэрэв await байгаа бол async def заавал.
Яагаад? Учир нь await нь зөвхөн async def дотор ажилладаг. Хэрэв та def дотор await бичвэл:
SyntaxError: 'await' outside async functionТэгэхээр логик нь энгийн: await хэрэгтэй бол async def; хэрэггүй бол def.
Энэ курсын код
Таны бүх endpoint def байсан. Яагаад? Учир нь бидний ашигласан хэрэгслүүд нь синхрон.
session.exec(...) — синхрон. session.commit() — синхрон. session.get(...) — синхрон.
SQLModel-ийн үндсэн хувилбар нь синхрон ажилладаг. Тиймээс await хэрэглэх зүйл байхгүй, тиймээс def зөв сонголт.
Хэрэв бид async өгөгдлийн сан ашигласан бол (SQLModel үүнийг дэмждэг, гэхдээ илүү төвөгтэй) код ийм байх байсан:
python
@router.get("/notes")
async def get_notes(session: AsyncSession = Depends(get_session)):
result = await session.exec(select(Note))
return result.all()await гарч ирсэн тул async def заавал болсон.
Гэхдээ энэ курсэд бид тэр замаар явахгүй. Async өгөгдлийн сан нь нэмэлт төвөг авчирдаг бөгөөд суралцахад тодорхой ашиг өгдөггүй.
Шударга хариу: ихэнх жижиг, дунд API-д хэрэггүй.
Async нь дараах тохиолдолд бодит ашиг өгдөг:
Маш их зэрэг хүсэлт. Секундэд мянга мянган хүсэлт хүлээж авдаг API. Тэр үед урсгал үүсгэх зардал мэдэгдэхүйц болно.
Олон гадаад дуудлага. Endpoint нь хэд хэдэн өөр API руу хандаж, тэдгээрийг зэрэг хүлээх шаардлагатай бол. Async-аар тэдгээрийг зэрэг илгээж, бүгдийг нэг дор хүлээж болно.
Урт хугацааны холболт. WebSocket, стриминг — тэдгээр нь async шаарддаг.
Эдгээр нь бүгд том хэмжээний асуудлууд. Таны эхний арван API-д тэдгээр гарах магадлал бага.
FastAPI-ийн албан ёсны баримт бичиг ч ижил зөвлөгөө өгдөг: эргэлзэж байвал def ашигла. Тэр нь үргэлж аюулгүй.
Одоо хамгийн чухал анхааруулга. Энэ бол async-тай холбоотой хамгийн түгээмэл, хамгийн ноцтой алдаа юм.
Дараах кодыг харъя:
python
import time
@app.get("/slow")
async def slow_endpoint():
time.sleep(5) # синхрон хүлээлт async функц дотор
return {"message": "Дууслаа"}time.sleep(5) — таван секунд хүлээнэ. Гэхдээ энэ бол синхрон хүлээлт: процессорыг барьж, юу ч хийхгүй суудаг.
Энэ нь async def дотор байгаа тул үндсэн урсгалд ажиллана. Тиймээс таван секундын турш бүх сервер зогсоно. Өөр ямар ч хүсэлт боловсруулагдахгүй.
Хэрэв арван хэрэглэгч зэрэг хандвал — тэд бүгд дараалан хүлээнэ. Нийт тавин секунд.
Ижил код def дээр
python
import time
@app.get("/slow")
def slow_endpoint():
time.sleep(5)
return {"message": "Дууслаа"}Одоо энэ нь тусдаа урсгалд ажиллана. Үндсэн урсгал чөлөөтэй. Арван хэрэглэгч зэрэг хандвал — арван урсгалд зэрэг ажиллаж, бүгд таван секундын дараа хариу авна.
Ижил код, огт өөр зан төлөв.
Туршиж үзэх
Хүсвэл өөрөө туршиж болно. main.py-д хоёр endpoint нэмнэ:
python
# main.py (түр зуурын туршилт)
import time
@app.get("/slow-async")
async def slow_async():
time.sleep(5)
return {"type": "async"}
@app.get("/slow-sync")
def slow_sync():
time.sleep(5)
return {"type": "sync"}
@app.get("/fast")
def fast():
return {"message": "Хурдан"}Browser дээр хоёр таб нээнэ.
Туршилт 1: Эхний табд /slow-async дуудна. Тэр ажиллаж байх зуур хоёр дахь табд /fast дуудна. /fast нь хүлээнэ — эхнийх дуустал хариу ирэхгүй.
Туршилт 2: Эхний табд /slow-sync дуудна. Хоёр дахь табд /fast дуудна. /fast шууд хариулна — эхнийх нь тусдаа урсгалд ажиллаж байгаа тул.
Энэ туршилт нь ялгааг гар дээрээ мэдрүүлнэ.
Туршилтын дараа эдгээр endpoint-ыг устгаарай.
Зөв async хүлээлт
Хэрэв та async def дотор хүлээх шаардлагатай бол async хувилбар ашиглах ёстой:
python
import asyncio
@app.get("/slow-async-correct")
async def slow_async_correct():
await asyncio.sleep(5) # await-тай, async хүлээлт
return {"message": "Дууслаа"}asyncio.sleep нь time.sleep-ээс өөр: тэр нь процессорыг сулладаг. Хүлээж байх зуур сервер өөр хүсэлт боловсруулж чадна.
await гарч ирснийг анзаараарай — энэ бол "энд хүлээж байна, өөр ажил хий" гэсэн дохио.
Дүрэм: async def дотор зөвхөн await-тай хүлээлт ашигла. Синхрон хүлээлт (time.sleep, файл унших, синхрон өгөгдлийн сангийн дуудлага) нь бүх серверийг блоклоно.
Нэг чухал зүйл: энэ дүрэм dependency-д ч хамаарна.
python
# Синхрон dependency — тусдаа урсгалд
def get_session():
with Session(engine) as session:
yield session
# Async dependency — үндсэн урсгалд
async def get_something():
return await fetch_something()FastAPI нь dependency-г ч ижил аргаар зохицуулдаг: def бол тусдаа урсгалд, async def бол үндсэн урсгалд.
Таны get_session нь def — зөв, учир нь SQLModel-ийн session нь синхрон.
Мөн та тэдгээрийг чөлөөтэй хольж болно. async def endpoint нь def dependency ашиглаж чадна, эсрэгээр ч болно. FastAPI бүгдийг зохицуулна.
Async бодит ашиг өгдөг нэг тодорхой жишээ харъя. Endpoint нь хэд хэдэн гадаад API руу хандах шаардлагатай гэж бодъё.
Синхрон хувилбар:
python
import requests
@app.get("/weather-all")
def weather_all():
ub = requests.get("https://api.example.com/weather/ulaanbaatar").json()
dh = requests.get("https://api.example.com/weather/darkhan").json()
ed = requests.get("https://api.example.com/weather/erdenet").json()
return {"ulaanbaatar": ub, "darkhan": dh, "erdenet": ed}Гурван дуудлага дараалан явна. Тус бүр нь нэг секунд авдаг бол нийт гурван секунд.
Async хувилбар:
python
import asyncio
import httpx
@app.get("/weather-all")
async def weather_all():
async with httpx.AsyncClient() as client:
results = await asyncio.gather(
client.get("https://api.example.com/weather/ulaanbaatar"),
client.get("https://api.example.com/weather/darkhan"),
client.get("https://api.example.com/weather/erdenet"),
)
return {
"ulaanbaatar": results[0].json(),
"darkhan": results[1].json(),
"erdenet": results[2].json(),
}asyncio.gather — гурван дуудлагыг зэрэг илгээж, бүгдийг хүлээнэ. Нийт нэг секунд (хамгийн удаанынх нь).
Гурван дахин хурдан.
Анзаараарай: requests биш httpx ашиглаж байна. requests нь синхрон тул async def дотор ашиглах нь блоклоно. httpx нь async хувилбартай.
Энэ код ажиллуулах шаардлагагүй — зөвхөн async хэзээ бодит ашиг өгдгийн жишээ. Хэрэв та ирээдүйд ийм зүйл бичих шаардлагатай болвол async сурах цаг болсон гэсэн үг.
Курс 2-ын Түвшин 4-ийн бүтээн байгуулалт — Цаг агаарын программ — санаж байна уу? Тэнд та requests ашиглан API руу хандаж байсан.
Тэр программ синхрон байсан бөгөөд тэр нь зөв байсан. Ганц хэрэглэгч, ганц дуудлага, хүлээх нь асуудалгүй.
Одоо та ширээний нөгөө талд байна. Таны API руу олон хүн хандаж болно. Тиймээс "хүлээх нь бусдад нөлөөлөх үү?" гэсэн асуулт гарч ирдэг.
Хариу нь: def ашиглавал нөлөөлөхгүй. FastAPI таны өмнөөс зохицуулна.
Энэ хичээлийг эргэн харъя. Юу сурсан бэ?
Та async бичих шаардлагагүй. Энэ курсын бүх код def ашигласан бөгөөд тэр нь зөв, найдвартай, ажиллагаатай.
Та async-ыг таних ёстой. Интернэтээс жишээ хайхад async def байнга харагдана. Одоо та түүнийг хараад "аа, энэ нь ижил зүйл, зөвхөн өөр горимд ажиллаж байна" гэж ойлгоно.
Та дүрмийг мэдэх ёстой. await хэрэгтэй бол async def; үгүй бол def. async def дотор синхрон хүлээлт бүү тавь.
Энэ гурав хангалттай. Async-ыг гүнзгий эзэмших нь өөрөө том сэдэв бөгөөд Бүлэг 15-т бид түүнийг цаашдын зам болгон нэрлэхгүй — учир нь ихэнх хөгжүүлэгчид түүнийг гүнзгий сурах шаардлагагүй байдаг.
Курс 2-ын Бүлэг 14-т би "async-ийн мастер болох шаардлагагүй, FastAPI-г эхлүүлэхэд таних хангалттай" гэж хэлсэн. Энэ хичээл тэр өгүүлбэрийг баталж байна.
def дотор await
python
@app.get("/notes")
def get_notes():
result = await something() # SyntaxErrorГаралт:
SyntaxError: 'await' outside async functionawait нь зөвхөн async def дотор ажиллана. Засвар: функцийг async def болгох.
async def дотор time.sleep
Дээр дэлгэрэнгүй үзсэн. Бүх серверийг блоклоно. Алдаа гарахгүй — зөвхөн сервер удаан болно, шалтгааныг олоход хэцүү.
Засвар: def ашиглах, эсвэл await asyncio.sleep(...).
async def дотор requests
python
@app.get("/weather")
async def weather():
response = requests.get("https://...") # синхрон!
return response.json()requests нь синхрон тул сүлжээ хүлээх зуур бүх сервер блоклогдоно. Ижил асуудал, өөр хэлбэр.
Засвар: def ашиглах (хамгийн энгийн), эсвэл httpx руу шилжих.
async-ыг "хурдан" гэж ойлгох
Нэг эндүүрэл: "async нь хурдан тул үргэлж ашиглах ёстой".
Буруу. Async нь нэг хүсэлтийг хурдан болгодоггүй. Тэр нь олон хүсэлтийг зэрэг зохицуулах чадварыг сайжруулдаг. Нэг хүсэлт нь ижил хугацаа авна.
Мөн def endpoint ч гэсэн олон хүсэлтийг зэрэг зохицуулна (тусдаа урсгалд). Ялгаа нь зөвхөн маш их ачаалалд мэдэгддэг.
Бүх зүйлийг async болгох
python
@app.get("/notes")
async def get_notes(session: Session = Depends(get_session)):
return session.exec(select(Note)).all() # синхрон дуудлага!Энэ нь алдаа. async def гэж бичсэн ч дотор нь синхрон өгөгдлийн сангийн дуудлага байна. Тэр нь үндсэн урсгалыг блоклоно.
Хэрэв та SQLModel-ийн синхрон session ашиглаж байгаа бол endpoint нь def байх ёстой. async def бичих нь зөвхөн муутгана.
Энэ бол шинэ хөгжүүлэгчдийн түгээмэл алдаа: "бүгд async бичдэг тул би ч бас бичье" гэж бодоод, ойлголтгүйгээр хуулж, гүйцэтгэлийг мууруулах.
Хүсвэл дээр дурдсан /slow-async болон /slow-sync туршилтыг хийж үзээрэй. Хоёр таб нээж, ялгааг өөрийн нүдээр харах нь энэ хичээлийн гол санааг бататгана. Туршилтын дараа endpoint-үүдийг устгаарай.
Сонирхвол одоогийн Тэмдэглэлийн API-ийн нэг endpoint-ыг async def болгож үзээрэй:
python
@router.get("", response_model=list[NotePublic])
async def get_notes(...):
...Ажиллана — алдаа гарахгүй. Гэхдээ дотор нь синхрон session.exec байгаа тул энэ нь сайжруулалт биш, доройтуулалт юм. Туршсаны дараа def рүү буцаана.
FastAPI нь def болон async def хоёуланг дэмждэг — аль нь ч буруу биш.
async def -> үндсэн урсгалд (хурдан, гэхдээ блоклох эрсдэлтэй). def -> тусдаа урсгалд (аюулгүй).
Практик дүрэм: await хэрэглэх зүйл байхгүй бол def ашигла.
Энэ курсын код def ашигласан нь зөв — SQLModel-ийн session нь синхрон.
Хамгийн ноцтой алдаа: async def дотор синхрон удаан ажил (time.sleep, requests, синхрон DB) — бүх серверийг блоклоно.
async def дотор зөвхөн await-тай хүлээлт (asyncio.sleep, httpx).
Async бодит ашиг өгдөг: маш их зэрэг хүсэлт, олон гадаад дуудлагыг зэрэг (asyncio.gather), WebSocket.
Dependency-д ч ижил дүрэм; def болон async def-ыг чөлөөтэй хольж болно.
Async нь нэг хүсэлтийг хурдан болгодоггүй — олон хүсэлтийг зэрэг зохицуулах чадварыг сайжруулна.
Эргэлзэж байвал def. FastAPI-ийн албан ёсны зөвлөгөө ч ижил.
Async-ийн асуулт хаагдлаа. Дараагийн хичээлд бид огт өөр сэдэв рүү орно: CORS. Энэ бол browser-ийн аюулгүй байдлын дүрэм бөгөөд таны API-д вэб хуудаснаас хандах гэж оролдох мөчид гэнэт гарч ирдэг. Одоохондоо та API-даа зөвхөн /docs болон хаягийн мөрөөр ханддаг тул түүнтэй тааралдаагүй. Гэхдээ хэн нэгэн (эсвэл та өөрөө) frontend бичих өдөр CORS нь эхний саад болно. Бид түүнийг урьдчилан ойлгож, шийдлийг нь нэг мөрөөр авна.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.