Ачааллж байна...
Энэ хичээлээр Бүлэг 10 дуусна. Мөн энэ бол курсийн эргэлтийн цэгүүдийн нэг байх болно.
Учир нь та FastAPI-гийн кодыг АНХ УДАА харах гэж байна. Тэр нь энэ курсийн эцсийн зорилго — Бүлэг 15-ын capstone. Одоохондоо бид түүнийг АЖИЛЛУУЛАХГҮЙ, зөвхөн УНШИНА.
Гэвч тэр унших нь илбэ мэт санагдахгүй. Учир нь та одоо @ тэмдгийн ард юу байгааг мэдэж байна.
Энэ хичээлийн зорилго нь маш тодорхой: @app.get("/") гэсэн мөрийг харахад "аан, тэр нь функцийг бүртгэж байна" гэж шууд ойлгох.
Гэхдээ FastAPI руу шууд үсрэхгүй. Эхлээд бид өөрсдийн жижиг "framework" бичнэ. Тэгвэл FastAPI-г харахад бүх зүйл танил байх болно.
Эхлээд асуудлыг ойлгоцгооё.
Курсийн эхний хичээлд (Бүлэг 1.1) би framework -ийг ингэж тайлбарласан:
"Та
importашиглаж бусдын кодыг ДУУДАЖ байсан. Framework бол үүний ЭСРЭГ: та кодоо бичээд өгдөг, гэхдээ түүнийг хэзээ дуудахыг framework өөрөө шийддэг. Та хүлээж байдаг, тэр дууддаг."
Тэр үед энэ нь хийсвэр сонсогдсон байх. Одоо түүнийг БОДИТ жишээгээр харъя.
Та вэб сервер бичиж байна гэж бодъё. Хэн нэгэн интернэтээр таны сайт руу хандахад — тэр нь ТОДОРХОЙ ХАЯГ руу ханддаг:
/ — нүүр хуудас
/nomnuud — номнуудын жагсаалт
/gishuud — гишүүдийн жагсаалт
Хаяг бүрд ӨӨР функц ажиллах ёстой.
Асуудал: сервер нь ЯМАР функцийг ЯМАР хаяг дээр дуудахаа яаж мэдэх вэ?
Хамгийн энгийн шийдэл нь:
python
def nuur_khuudas() -> str:
return "Тавтай морил"
def nomnuud() -> str:
return "Номнуудын жагсаалт"
def gishuud() -> str:
return "Гишүүдийн жагсаалт"
def khuselt_boloviruulakh(zam: str) -> str:
if zam == "/":
return nuur_khuudas()
elif zam == "/nomnuud":
return nomnuud()
elif zam == "/gishuud":
return gishuud()
else:
return "404 — Олдсонгүй"
print(khuselt_boloviruulakh("/"))
print(khuselt_boloviruulakh("/nomnuud"))
print(khuselt_boloviruulakh("/baihgui"))Тавтай морил
Номнуудын жагсаалт
404 — ОлдсонгүйАжиллаж байна. Гэвч энэ бол МУУ дизайн.
Шалтгаан 1: шинэ хуудас нэмэхэд ХОЁР газар засах хэрэгтэй. Функцээ бичээд, дараа нь if/elif гинжид нэмэх хэрэгтэй. Нэгийг мартвал — функц хэзээ ч дуудагдахгүй.
Шалтгаан 2: гинж УРТАСНА. Зуун хуудас байвал — зуун elif. Уншихад аймшигтай.
Шалтгаан 3: мэдлэг ТАРХСАН. "Энэ функц нь /nomnuud хаягт хамаарна" гэсэн мэдээлэл нь функцээс ХОЛ, өөр газарт байна.
Бүлэг 7.3-ын сургамжийг санаж байна уу?
"Хэрэв та
isinstance-ээр төрөл шалгаж, дараа нь өөр өөр зүйл хийж байвал — тэр нь ихэвчлэн override ашиглах ёстой байсны шинж юм."
Энд ижил зарчим: if/elif гинж нь ямар нэг илүү сайн механизм хэрэгтэй байгаагийн шинж.
Бүлэг 3.5-ын сургамжийг санаж байна уу?
"Dictionary дахь функцууд нь урт
if/elifгинжийг орлож чадна."
python
def nuur_khuudas() -> str:
return "Тавтай морил"
def nomnuud() -> str:
return "Номнуудын жагсаалт"
def gishuud() -> str:
return "Гишүүдийн жагсаалт"
# ЗАМ → ФУНКЦ гэсэн харгалзаа
ZAMUUD: dict[str, callable] = {
"/": nuur_khuudas, # ХААЛТГҮЙ!
"/nomnuud": nomnuud,
"/gishuud": gishuud,
}
def khuselt_boloviruulakh(zam: str) -> str:
func = ZAMUUD.get(zam)
if func is None:
return "404 — Олдсонгүй"
return func()
print(khuselt_boloviruulakh("/"))
print(khuselt_boloviruulakh("/nomnuud"))
print(khuselt_boloviruulakh("/baihgui"))Тавтай морил
Номнуудын жагсаалт
404 — ОлдсонгүйХамаагүй дээр.
ZAMUUD нь зам → функц гэсэн харгалзаа. Функцууд нь ХААЛТГҮЙ хадгалагдсан (Бүлэг 3.5: "дамжуулахад хаалтгүй").
khuselt_boloviruulakh нь одоо ЯМАР Ч if/elif -гүй. Тэр зөвхөн dictionary-аас функцийг олж, дуудаж байна.
.get(zam) нь олдохгүй бол None буцаана (Бүлэг 4.3), тиймээс 404 -ыг барьж чадаж байна.
Гэвч нэг асуудал үлдлээ: ZAMUUD dictionary-г ГАРААР бичих хэрэгтэй. Шинэ функц нэмэхэд — dictionary-д ч бас нэмэх хэрэгтэй. Хоёр газар засах гэсэн асуудал хэвээрээ.
Одоо decorator орж ирнэ.
python
from typing import Callable
ZAMUUD: dict[str, Callable[[], str]] = {}
def zam(khayag: str):
def decorator(func: Callable[[], str]) -> Callable[[], str]:
ZAMUUD[khayag] = func # БҮРТГЭЖ байна!
return func # функцийг ӨӨРЧЛӨХГҮЙГЭЭР буцаана
return decorator
@zam("/")
def nuur_khuudas() -> str:
return "Тавтай морил"
@zam("/nomnuud")
def nomnuud() -> str:
return "Номнуудын жагсаалт"
@zam("/gishuud")
def gishuud() -> str:
return "Гишүүдийн жагсаалт"
def khuselt_boloviruulakh(khayag: str) -> str:
func = ZAMUUD.get(khayag)
if func is None:
return "404 — Олдсонгүй"
return func()
print(ZAMUUD)
print()
print(khuselt_boloviruulakh("/"))
print(khuselt_boloviruulakh("/nomnuud"))
print(khuselt_boloviruulakh("/baihgui")){'/': <function nuur_khuudas at 0x...>, '/nomnuud': <function nomnuud at 0x...>, '/gishuud': <function gishuud at 0x...>}
Тавтай морил
Номнуудын жагсаалт
404 — ОлдсонгүйЭНЭ БОЛ FRAMEWORK.
Жижиг, энгийн, гэхдээ жинхэнэ. Одоо түүнийг задалъя, учир нь энд ГУРВАН чухал зүйл байна.
Нэгдүгээрт, @zam("/") нь ХААЛТТАЙ.
Бүлэг 10.2-т би "decorator нь хаалтгүй" гэж хэлсэн. Тэгвэл яагаад энд хаалттай байна вэ?
Учир нь zam нь decorator БИШ — тэр нь decorator ҮҮСГЭДЭГ функц юм.
Задалъя:
python
@zam("/")
def nuur_khuudas() -> str:
...нь дараах кодтой ижил:
python
def nuur_khuudas() -> str:
...
nuur_khuudas = zam("/")(nuur_khuudas)Хоёр дуудалт байгааг анзаараарай: zam("/") дараа нь (nuur_khuudas).
zam("/") нь decorator буцаана (дотоод decorator функц)
Дараа нь тэр decorator нь nuur_khuudas -ыг хүлээж авна
Тэгэхээр zam нь "decorator үйлдвэрлэдэг үйлдвэр" юм. Тэр нь closure ашиглаж khayag -ыг САНАЖ байна (Бүлэг 10.1).
Энэ бол аргументтай decorator-ын хэв маяг. Бүлэг 10.2-т би түүнийг товч дурдаад "энэ курсэд шаардлагагүй" гэж хэлсэн. Одоо тэр шаардлагатай болов.
Хоёрдугаарт, decorator нь функцийг ӨӨРЧЛӨХГҮЙ.
python
def decorator(func):
ZAMUUD[khayag] = func # бүртгэнэ
return func # ЯГ ТЭР функцийг буцаанаАнзаараарай: боолт БАЙХГҮЙ. Дотоод dotood функц байхгүй. *args, **kwargs байхгүй.
Тэр нь зөвхөн:
Функцийг dictionary-д БҮРТГЭНЭ
Функцийг ЯГ ТЭР ХЭВЭЭР нь буцаана
Тэгэхээр nuur_khuudas нь боогдоогүй! Түүнийг шууд дуудахад ердийнхөөрөө ажиллана:
python
print(nuur_khuudas())Тавтай морилЭнэ бол чухал ойлголт. Decorator-ын ГАНЦ зорилго нь заавал функцийг өөрчлөх БИШ. Тэр нь зөвхөн ХАЖУУГИЙН ҮР ДАГАВАР үүсгэж болно — энд тэр нь бүртгэл.
Гуравдугаарт, бүртгэл нь ХЭЗЭЭ болдог вэ?
Хамгийн чухал асуулт.
python
print("Программ эхлэв")
@zam("/")
def nuur_khuudas() -> str:
print("nuur_khuudas ажиллав")
return "Тавтай морил"
print(f"ZAMUUD: {list(ZAMUUD.keys())}")
print("Одоохондоо функц дуудагдаагүй")Программ эхлэв
ZAMUUD: ['/']
Одоохондоо функц дуудагдаагүйАнзаараарай: "nuur_khuudas ажиллав" НЭГ Ч удаа хэвлэгдээгүй.
Гэвч ZAMUUD -д тэр аль хэдийн БҮРТГЭГДСЭН.
Тэгэхээр:
@zam("/") нь файл АЧААЛАГДАХ үед ажиллана — функцийг бүртгэнэ
nuur_khuudas() нь ХЭЗЭЭ Ч дуудагдахгүй — хэрэв хэн нэгэн / хаяг руу хандахгүй бол
Энэ бол framework-ийн МӨН ЧАНАР.
Та функцээ бичээд, бүртгүүлээд, дараа нь ХҮЛЭЭНЭ. Framework нь түүнийг хэзээ дуудахаа өөрөө шийднэ.
Бүлэг 1.1-ийн өгүүлбэрийг санаж байна уу?
"Та хүлээж байдаг, тэр дууддаг."
Одоо та түүнийг өөрийн нүдээр харлаа.
Бодит вэб сервер нь зөвхөн ЗАМ-аар биш, HTTP METHOD -ээр ч ялгадаг.
Бүлэг 11-д бид тэдгээрийг бүрэн үзнэ. Одоохондоо зөвхөн хоёрыг мэдэхэд хангалттай:
GET — "надад өгөгдөл ӨГ" (унших)
POST — "энэ өгөгдлийг АВ" (бичих)
Тэгэхээр /nomnuud хаяг нь ХОЁР өөр функцтэй байж болно:
GET /nomnuud — номнуудыг жагсаа
POST /nomnuud — шинэ ном нэм
Бидний framework-ыг өргөтгөе:
python
from typing import Callable
ZAMUUD: dict[tuple[str, str], Callable] = {}
def get(khayag: str):
def decorator(func: Callable) -> Callable:
ZAMUUD[("GET", khayag)] = func
return func
return decorator
def post(khayag: str):
def decorator(func: Callable) -> Callable:
ZAMUUD[("POST", khayag)] = func
return func
return decorator
@get("/nomnuud")
def nomnuud_jagsaakh() -> str:
return "Номнуудын жагсаалт"
@post("/nomnuud")
def nom_nemekh() -> str:
return "Шинэ ном нэмэгдлээ"
@get("/")
def nuur_khuudas() -> str:
return "Тавтай морил"
def khuselt_boloviruulakh(method: str, khayag: str) -> str:
func = ZAMUUD.get((method, khayag))
if func is None:
return "404 — Олдсонгүй"
return func()
print(khuselt_boloviruulakh("GET", "/"))
print(khuselt_boloviruulakh("GET", "/nomnuud"))
print(khuselt_boloviruulakh("POST", "/nomnuud"))
print(khuselt_boloviruulakh("DELETE", "/nomnuud"))Тавтай морил
Номнуудын жагсаалт
Шинэ ном нэмэгдлээ
404 — ОлдсонгүйИжил зам, өөр method → өөр функц.
ZAMUUD -ийн key нь одоо TUPLE байгааг анзаараарай: ("GET", "/nomnuud").
Бүлэг 5.1-ийн сургамжийг санаж байна уу?
"Tuple нь dictionary-ийн key болж чадна. Хосолсон key (сар + ангилал) -д хамгийн цэвэрхэн шийдэл."
Энд яг тэр: method + зам гэсэн хосолсон key.
Бүлэг 5-ын мэдлэг framework барихад ажиллаж байна.
Одоо жинхэнэ FastAPI-гийн кодыг харъя.
АНХААР: бид энэ кодыг АЖИЛЛУУЛАХГҮЙ. Тэр нь Бүлэг 15-ын capstone. Одоохондоо зөвхөн УНШИНА.
python
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def nuur_khuudas():
return {"mesej": "Тавтай морил"}
@app.get("/nomnuud")
def nomnuud_jagsaakh():
return {"nomnuud": ["Монголын нууц товчоо", "Цагаан хэрэм"]}Танил харагдаж байна уу?
Байх ЁСТОЙ. Учир нь энэ нь бидний дөнгөж сая бичсэн зүйлтэй БАРАГ ЯГ ИЖИЛ.
Мөр мөрөөр нь харьцуулъя:
Бидний framework | FastAPI |
|---|---|
|
|
|
|
|
|
| (дотор нь ижил зүйл) |
Ганц ялгаа нь: FastAPI-д decorator нь app объектын method юм (app.get), харин бидэнд тэр нь энгийн функц байсан (get).
Яагаад? Учир нь FastAPI нь бүртгэлийг ГЛОБАЛ dictionary-д биш, app объектын дотор хадгалдаг. Тэр нь илүү цэвэрхэн — та нэг программд хэд хэдэн app үүсгэж чадна.
Гэхдээ ЗАРЧИМ нь ЯГ ИЖИЛ:
@app.get("/")гэсэн мөр нь функцийг АВЧ,app-ийн дотоод бүртгэлд("GET", "/")гэсэн key -ээр хадгалж, функцийг ӨӨРЧЛӨЛГҮЙГЭЭР буцаана.
Илбэ БАЙХГҮЙ.
Одоо FastAPI-гийн бүрэн жишээг харъя. Энэ бол Бүлэг 15-д та бичих кодтой ойролцоо.
python
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Nom(BaseModel):
garchig: str
zokhiolch: str
on: int
khuudas: int = 0
nomnuud: list[Nom] = []
@app.get("/")
def nuur_khuudas() -> dict[str, str]:
return {"mesej": "Номын сангийн API"}
@app.get("/nomnuud")
def nomnuud_jagsaakh() -> list[Nom]:
return nomnuud
@app.post("/nomnuud")
def nom_nemekh(nom: Nom) -> Nom:
nomnuud.append(nom)
return nomОдоо ЭНЭ кодыг УНШААРАЙ. Та юуг мэдэж байна вэ?
class Nom(BaseModel): — Бүлэг 9.2-оос: "dataclass + type hint + default утга = ЯГ ТЭР бүтэц, Pydantic ашигладаг." Тэр нь @dataclass -тай бараг ижил, зөвхөн BaseModel -ээс өвлөж байна (inheritance — Бүлэг 7).
garchig: str, khuudas: int = 0 — type hint (Бүлэг 4), default утга (Бүлэг 3.1).
@app.get("/") — decorator (энэ хичээл). Функцийг бүртгэж байна.
-> dict[str, str], -> list[Nom] — буцаах төрлийн hint (Бүлэг 4.4).
def nom_nemekh(nom: Nom) -> Nom: — параметр нь Nom төрөлтэй. FastAPI нь интернэтээс ирсэн JSON -ыг АВТОМАТААР Nom объект болгож хувиргана — яг л Бүлэг 9.3-т бидний ГАРААР хийсэн зүйл.
nomnuud.append(nom) — ердийн list (Курс 1).
Танд ойлгомжгүй зүйл байна уу?
Магадгүй BaseModel -ийн нарийвчлал. Магадгүй FastAPI яаж сервер ажиллуулдаг. Гэхдээ КОДЫН БҮТЭЦ нь бүхэлдээ танил.
Энэ бол энэ курсийн зорилго байсан.
Бүлэг 1.1-д би ингэж хэлсэн:
"Сургалтын төгсгөлд та FastAPI гэх мэт framework-ийн албан ёсны documentation-ыг нээгээд, уншаад, ойлгодог болно."
Та одоо тэнд байна. Бүлэг 15 хүртэл дөрөв дэх бүлэг л үлдлээ, гэхдээ гол ойлголтууд аль хэдийн таны гарт байна.
FastAPI бол цорын ганц биш. Decorator нь Python-ы framework ертөнцөд хаа сайгүй байдаг.
Flask (өөр нэг вэб framework):
python
@app.route("/")
def nuur_khuudas():
return "Тавтай морил"pytest (тест framework):
python
@pytest.fixture
def nomiin_san():
return NomiinSan("Тест")Click (командын мөрийн framework):
python
@click.command()
@click.option("--ner", help="Хэрэглэгчийн нэр")
def mendchilekh(ner: str):
click.echo(f"Сайн байна уу, {ner}")Бүгд ижил зарчим: decorator нь функцийг БҮРТГЭНЭ, framework нь түүнийг ХЭЗЭЭ дуудахаа шийднэ.
Та тэдгээрийг харах бүрдээ айхаа болино. Тэр нь зөвхөн func = decorator(func) юм.
Хэрэв сонирхвол: дээрх жижиг framework-ыг өөрөө бичээд, шинэ зам нэмээд үзээрэй. Дараа нь print(ZAMUUD) гэж бичээд, бүртгэл нь функц ДУУДАГДАХААС ӨМНӨ дүүрсэн байгааг өөрийн нүдээр хараарай.
Дараа нь функцийг ШУУД дуудаад (nuur_khuudas()), тэр нь ердийнхөөрөө ажиллаж байгааг батлаарай — decorator нь түүнийг ӨӨРЧЛӨӨГҮЙ.
Мөн хүсвэл: DELETE method нэмээд, @delete("/nomnuud") decorator бичээд үзээрэй.
1. Аргументтай decorator-д хаалт мартах
python
@zam # хаалтгүй!
def nuur_khuudas() -> str:
return "Тавтай морил"TypeError: zam.<locals>.decorator() missing 1 required positional argument: 'func'Түр зогсъё — үнэндээ энэ алдаа өөр байх болно. @zam гэж бичихэд zam(nuur_khuudas) дуудагдана, тэгэхээр khayag нь функц болно. Дараа нь тэр decorator буцаана. Тэгэхээр nuur_khuudas нь decorator функц болж хувирна — алдаа шууд гарахгүй, гэхдээ бүх зүйл эвдэрнэ.
Ийм чимээгүй алдаа нь хамгийн аюултай. Аргументтай decorator нь ЗААВАЛ хаалттай: @zam("/").
2. Аргументгүй decorator-д хаалт бичих
python
@khugatsaa_khemjikh() # хаалттай!
def func(): ...TypeError: khugatsaa_khemjikh() missing 1 required positional argument: 'func'Эсрэг алдаа. Ердийн decorator нь хаалтгүй: @khugatsaa_khemjikh.
Дүрэм: decorator нь аргумент авдаг эсэхийг мэдэх ёстой. @app.get("/") — аргументтай. @property — аргументгүй.
3. Функцийг буцаахаа мартах
python
def zam(khayag: str):
def decorator(func):
ZAMUUD[khayag] = func
# return func ← МАРТСАН!
return decorator
@zam("/")
def nuur_khuudas() -> str:
return "Тавтай морил"
print(nuur_khuudas())TypeError: 'NoneType' object is not callabledecorator нь юу ч буцаагаагүй тул nuur_khuudas нь None болсон. Бүртгэл нь ажилласан, гэхдээ функц алга болсон.
Санамж: decorator нь ЗААВАЛ ямар нэг зүйл буцаах ёстой — эх функцийг эсвэл боолтыг.
4. Бүртгэл нь функцийг АЖИЛЛУУЛНА гэж бодох
python
@zam("/")
def nuur_khuudas() -> str:
print("Би ажиллав!")
return "Тавтай морил"Файл ачаалагдахад "Би ажиллав!" хэвлэгдэх ҮҮ?
ҮГҮЙ. Decorator нь функцийг зөвхөн БҮРТГЭНЭ. Дуудахгүй.
5. Ижил зам хоёр удаа бүртгэх
python
@zam("/")
def negdugeer() -> str:
return "Нэг"
@zam("/")
def hoyordugaar() -> str:
return "Хоёр"Алдаа гарахгүй! Гэвч ZAMUUD["/"] нь сүүлчийнхийг заана. Эхнийх нь ЧИМЭЭГҮЙ дарагдсан.
Бүлэг 2.2-ын сургамж: dictionary-д key давхардвал сүүлчийнх нь өмнөхийг дарж бичнэ.
FastAPI нь энэ тохиолдолд анхааруулга өгдөг. Гэхдээ өөрийн framework бичихэд шалгалт нэмэх нь зүйтэй.
if/elif гинж нь "илүү сайн механизм хэрэгтэй" гэсэн шинж. Dictionary дахь функцууд түүнийг орлоно (Бүлэг 3.5).
Гэхдээ dictionary-г ГАРААР дүүргэх нь "хоёр газар засах" асуудал үүсгэнэ. Decorator түүнийг шийднэ.
Бүртгэх decorator нь функцийг ӨӨРЧЛӨХГҮЙ:
python
def zam(khayag: str):
def decorator(func):
ZAMUUD[khayag] = func # бүртгэнэ
return func # ЯГ ТЭР функцийг буцаана
return decoratorАргументтай decorator нь ХААЛТТАЙ: @zam("/"). Тэр нь "decorator ҮҮСГЭДЭГ функц" — closure -ээр аргументаа санана (Бүлэг 10.1).
@zam("/") нь func = zam("/")(func) -тэй ижил. ХОЁР дуудалт.
ХАМГИЙН ЧУХАЛ: бүртгэл нь файл АЧААЛАГДАХ үед болно, функц ДУУДАГДАХ үед биш. Та функцээ бүртгүүлээд ХҮЛЭЭНЭ. Framework нь түүнийг хэзээ дуудахаа өөрөө шийднэ.
Тэр бол framework-ийн МӨН ЧАНАР — Бүлэг 1.1-ийн "та хүлээж байдаг, тэр дууддаг."
@app.get("/") нь ЯГ ЭНЭ ЗҮЙЛ. FastAPI нь бүртгэлийг app объектын дотор хадгалдаг. Зарчим нь ижил.
Key нь TUPLE: ("GET", "/nomnuud") — method + зам (Бүлэг 5.1-ийн tuple-key).
Та одоо FastAPI-гийн кодыг УНШИЖ чадна. class Nom(BaseModel) (Бүлэг 9), type hint (Бүлэг 4), @app.get (Бүлэг 10), default утга (Бүлэг 3.1) — бүгд танил.
Flask, pytest, Click — бүгд ижил зарчим.
Бүлэг 10 дууслаа. @ тэмдэг нь эцэст нь тайлагдлаа. Та @property, @dataclass, @classmethod, @app.get — бүгдийг ойлгож байна. Мөн та өөрөө decorator бичиж чадна.
Дараагийн Бүлэг 11-д таны программ АНХ УДАА интернэттэй ярина.
Тэнд бид тэг мэдлэгээс эхэлнэ: интернэт хэрхэн ажилладаг вэ? Client, server, request, response гэж юу вэ? API гэж юу вэ? Дараа нь requests санг суулгаж (Бүлэг 1-ийн venv зуршил ажилд орно), жинхэнэ API-аас жинхэнэ өгөгдөл татна.
Тэр мөч — таны программ анх удаа гадаад ертөнцөөс мэдээлэл авах мөч — бол энэ курсийн хамгийн сэтгэл догдлом мөчүүдийн нэг байх болно.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.