Ачааллж байна...
Түвшин 4-т тавтай морил. Энэ түвшний нэр нь Төрөл, алдаа, хурд: мэргэжлийн Python кодын гурван чухал талыг нэгтгэнэ. Эхний бүлэг type hint-д зориулагдсан. Course 2-оос эхлэн та функцийн гарын үсэгт төрөл бичиж ирсэн: def greet(name: str) -> str:. Course 3-т FastAPI тэдгээр hint-үүдийг уншиж, хүсэлтийн параметрүүдийг шалгадаг байсан. Тэр үеийн hint-үүд ихэвчлэн энгийн байсан: int, str, list[str]. Энэ бүлэгт бид илүү нарийн хэлбэрүүдийг бичиж сурна.
Түвшин 3-ын бүтээлийн хоёр сул талыг санаарай. Үйл явдлын нэрийг “order_place” гэж үсгийн алдаатай бичихэд систем чимээгүй ажиллаж, сонсогч хэзээ ч дуудагдахгүй байсан. Event-ийн payload dict ямар түлхүүрүүдтэй байх ёстойг юу ч баталгаажуулдаггүй байсан. Энэ хичээлийн гурван хэрэгсэл яг ийм асуудлуудад зориулагдсан. Union нь "энэ утга эдгээр төрлийн аль нэг байна" гэдгийг илэрхийлнэ. Literal нь "энэ утга яг эдгээр тодорхой утгуудын аль нэг байна" гэдгийг илэрхийлнэ. TypedDict нь "энэ dict яг эдгээр түлхүүрүүдтэй, тус бүр ийм төрлийн утгатай" гэдгийг илэрхийлнэ.
Эхлээд нэг шударга, чухал баримт: Python type hint-ийг ажиллах үед шалгадаггүй. def greet(name: str) гэж бичсэн ч greet(42) гэж дуудвал Python ямар ч эсэргүүцэлгүйгээр ажиллуулна. Hint-үүд бол уншигчид, засварлагчид, тусгай шалгах хэрэгслүүдэд зориулсан мэдээлэл. Тэр хэрэгслүүдийн хамгийн алдартай нь mypy бөгөөд бид түүнийг Хичээл 10.4-т ажиллуулж, энэ хичээлд бичсэн hint-үүд жинхэнэ алдааг програм ажиллахаас өмнө хэрхэн олдгийг харна. Одоохондоо VS Code-ийн Python өргөтгөл зарим зөрчлийг улаан доогуур зураасаар харуулдаг. Энэ хичээлд бид hint-үүдийг бичих, мөн тэдгээрээс ажиллах үед хэрэгтэй мэдээллийг гаргаж авах аргыг сурна.
Төрийн байгууллагын албан ёсны маягтыг төсөөлөөд үзээрэй. Энгийн мэдүүлэгт "Нэр: ____" гэсэн хоосон зай байдаг: тэнд ямар ч текст бичиж болно. Энэ бол энгийн str hint. Гэвч сайн зохиосон маягт илүү нарийн байдаг. "Холбоо барих: утас эсвэл имэйл" гэсэн талбар хоёр өөр хэлбэрийн мэдээлэл хүлээн авна: энэ бол Union. "Гэрлэлтийн байдал: □ гэрлээгүй □ гэрлэсэн □ салсан" гэсэн талбарт өөрийн үгээр бичихгүй, бэлэн сонголтуудаас нэгийг тэмдэглэнэ: энэ бол Literal. Маягт бүхэлдээ тодорхой талбаруудтай, талбар бүр өөрийн хэлбэртэй: энэ бол TypedDict.
Маягтын нэг онцлогийг анзаараарай: маягт өөрөө хүнийг буруу бичихээс хүчээр сэргийлж чадахгүй. Хүн "гэрлэлтийн байдал" талбарт "мэдэхгүй" гэж бичиж болно, цаас эсэргүүцэхгүй. Маягтын хүч нь хоёр зүйлд оршино. Нэгдүгээрт, бөглөж буй хүнд юу хүлээгдэж байгааг тодорхой хэлдэг. Хоёрдугаарт, маягтыг хүлээн авах ажилтан түүнийг хүлээж аваад, буруу бөглөгдсөнийг шууд хардаг. Python-ий type hint бол маягт, mypy бол хүлээн авах ажилтан. Python өөрөө, програм ажиллах үед, маягтыг огт хардаггүй.
Энэ баримтыг эхлээд өөрийн нүдээр харъя:
python
def total_price(price: int, quantity: int) -> int:
return price * quantity
print(total_price(1500, 3))
print(total_price("1500", 3))
print(total_price.__annotations__)4500
150015001500
{'price': <class 'int'>, 'quantity': <class 'int'>, 'return': <class 'int'>}Хоёр дахь дуудлага string өгсөн. Python түүнийг ажиллуулж, "1500" * 3-ийг string-ийн давталт гэж үзэж, “150015001500” буцаасан. Ямар ч алдаа гараагүй, гэвч үр дүн утгагүй. Хэрэв энэ дүн дараа нь мөнгөн гүйлгээнд орвол алдаа огт өөр газарт, магадгүй хэдэн өдрийн дараа илэрнэ. Энэ бол hint-ийн тусламжтайгаар урьдчилан олох ёстой алдааны ангилал.
Гурав дахь мөр hint-үүд хаана хадгалагддагийг харуулж байна: функцийн __annotations__ dict-д. Python тэдгээрийг хадгалдаг ч ашигладаггүй. FastAPI шиг сангууд энэ dict-ийг өөрсдөө уншиж, hint-ийн дагуу ажилладаг. Course 3-т FastAPI таны endpoint-ийн параметрүүдийг шалгадаг байсан нь Python биш, FastAPI-ийн ажил байсан.
Жишээ 1: Union — "эсвэл" гэсэн төрөл
Бодит өгөгдөл ихэвчлэн нэг төрлөөр ирдэггүй. Хичээл 7.1-ийн from_dict-ийг санаарай: API-аас оноо заримдаа 95 гэсэн тоо, заримдаа “88” гэсэн string хэлбэрээр ирж байсан. Ийм утгыг хүлээн авдаг функцийн hint:
python
def parse_amount(value: int | str) -> int:
if isinstance(value, str):
value = value.replace(",", "").strip()
return int(value)
return value
print(parse_amount(25000))
print(parse_amount(" 12,500 "))25000
12500int | str нь “int эсвэл str” гэсэн утгатай. Энэ бичлэг Python 3.10-аас хойш ажилладаг бөгөөд одоогийн стандарт. | тэмдгийг Хичээл 7.4-ийн dict нэгтгэлт ба Хичээл 8.1-ийн operator-уудаас таньж байгаа: class-ууд ч __or__ method-той, int | str нь шинэ “union төрөл” объект үүсгэнэ. Хуучин кодонд та Union[int, str] гэсэн бичлэгийг typing модулиас импортлон хэрэглэсэн байхыг харна. Энэ нь ижил утгатай, хуучин хэлбэр бөгөөд таних хангалттай.
Функцийн биеийг анхааралтай хараарай. isinstance шалгалтын дараа Python, mypy хоёулаа нэг чухал зүйлийг ойлгоно: if блок дотор value бол str, тиймээс replace() ба strip() дуудах нь зөв. if-ийн дараа value бол int, учир нь str тохиолдол аль хэдийн return хийсэн. Үүнийг англиар type narrowing буюу "төрлийг нарийсгах" гэж нэрлэдэг. Union hint бичсэн бол функц тохиолдол бүрийг isinstance эсвэл ижил төстэй шалгалтаар тусад нь шийдэх ёстой бөгөөд mypy үүнийг шалгана. Хэрэв бид шалгалтгүйгээр value.replace(...) гэж бичвэл mypy “int-д replace байхгүй” гэж анхааруулна.
Union-ий хамгийн түгээмэл хэрэглээ бол X | None: "утга байж магадгүй, байхгүй ч байж болно". Хичээл 3.2-ын max(..., default=None) ба Хичээл 9.2-ын SHIPPING_METHODS.get(method) хоёулаа ийм утга буцаадаг:
python
from dataclasses import dataclass
@dataclass(frozen=True)
class Student:
name: str
score: int
def find_student(students: list[Student], name: str) -> Student | None:
for student in students:
if student.name == name:
return student
return None
roster = [Student("Бат", 95), Student("Сараа", 88)]
found = find_student(roster, "Ану")
if found is None:
print("Олдсонгүй")
else:
print(found.score)ОлдсонгүйStudent | None гарын үсэг уншигчид чухал анхааруулга өгнө: "буцаах утгыг шалгалгүйгээр бүү хэрэглэ". Хэрэв бид find_student(roster, "Ану").score гэж шууд бичвэл ажиллах үед “AttributeError: ‘NoneType’ object has no attribute ‘score’” гарна. Энэ бол Python-ий хамгийн түгээмэл алдаануудын нэг бөгөөд Хичээл 10.4-т mypy түүнийг ажиллуулахаас өмнө олохыг харна. is None шалгалт мөн type narrowing: else блок дотор found бол Student. Хуучин кодонд үүнийг Optional[Student] гэж бичдэг байсан, ижил утгатай.
Жишээ 2: Literal — бэлэн сонголтуудаас нэг
Хичээл 9.4-ийн үйл явдлын нэрс рүү буцъя. Тэнд бид ORDER_PLACED тогтмолыг үүсгэж, үсгийн алдааг NameError болгосон. Гэвч subscribe(event_name, ...) гэсэн гарын үсэг “event_name бол дурын string” гэж хэлж байсан. Literal-оор илүү нарийн хэлж болно:
python
from typing import Literal
EventName = Literal["order_placed", "order_cancelled"]
def subscribe(event_name: EventName, handler) -> None:
print(f"Бүртгэлээ: {event_name}")
subscribe("order_placed", print)
subscribe("order_place", print)Бүртгэлээ: order_placed
Бүртгэлээ: order_placeLiteral["order_placed", "order_cancelled"] нь "яг эдгээр хоёр string-ийн аль нэг" гэсэн төрөл. Бид түүнийг EventName гэсэн нэрэнд оноосон: энэ бол type alias буюу "төрлийн нэр", урт төрлийг нэг газарт тодорхойлж, олон газарт нэрээр нь хэрэглэх. Python 3.12-оос хойш үүнд тусгай бичлэг бий: type EventName = Literal["order_placed", "order_cancelled"]. Энгийн оноолт хуучин хувилбаруудад ч ажилладаг, хоёулаа бодит кодонд тааралдана.
Анхны жишээний сургамж энд ч хүчинтэй: “order_place” гэсэн буруу утга ажиллах үед ямар ч саадгүй өнгөрсөн. Гэвч VS Code аль хэдийн тэр мөрийг улаанаар тэмдэглэх бөгөөд Хичээл 10.4-т mypy “order_place” нь EventName-д тохирохгүй гэж тодорхой хэлнэ. Тогтмол нь зөвхөн бидний бичсэн кодыг хамгаалдаг байсан бол Literal гарын үсэгт тусгагдсан тул функцийг дуудаж буй хэн ч гэрээг харна.
Literal-ийн бас нэг давуу тал: түүнээс ажиллах үед хэрэгтэй жагсаалтыг гаргаж болно. Хичээл 7.4-ийн LogRecord VALID_LEVELS = ("INFO", "WARNING", "ERROR") гэсэн тусдаа tuple-тэй байсан. Түвшнүүдийн жагсаалт одоо хоёр газар байх ёстой мэт санагдана: type hint-д ба шалгалтад. typing модулийн get_args() энэ давхардлыг арилгана:
python
from dataclasses import dataclass
from typing import Literal, get_args
Level = Literal["INFO", "WARNING", "ERROR"]
VALID_LEVELS = get_args(Level)
@dataclass(frozen=True)
class LogRecord:
time: str
level: Level
user: str
def __post_init__(self):
if self.level not in VALID_LEVELS:
raise ValueError(f"Танигдаагүй түвшин: {self.level}. Боломжтой: {VALID_LEVELS}")
print(VALID_LEVELS)
print(LogRecord("08:15:40", "ERROR", "saraa"))
LogRecord("08:15:41", "DEBUG", "bat")('INFO', 'WARNING', 'ERROR')
LogRecord(time='08:15:40', level='ERROR', user='saraa')
Traceback (most recent call last):
...
ValueError: Танигдаагүй түвшин: DEBUG. Боломжтой: ('INFO', 'WARNING', 'ERROR')get_args(Level) нь Literal-ийн доторх утгуудыг tuple болгон буцаасан. Одоо түвшнүүдийн жагсаалт ганц газарт, Level-д амьдарна. Type hint mypy-д, VALID_LEVELS ажиллах үеийн шалгалтад үйлчилнэ. Шинэ түвшин нэмэхэд нэг мөр засна. Энэ бол type hint ба ажиллах үеийн шалгалтыг хослуулах эрүүл загвар: hint нь бидний кодын алдааг өмнө нь олно, __post_init__ гаднаас ирэх, mypy хэзээ ч харахгүй өгөгдлийг, жишээлбэл файл эсвэл API-аас ирэх, шалгана.
Жишээ 3: TypedDict — dict-ийн маягт
Хичээл 3.4-ийн log боловсруулагчийн parse_line dict буцаадаг байсан: date, time, level, user, action, duration_ms. Тэр dict-ийн бүтцийг зөвхөн кодыг уншиж мэдэх боломжтой байсан, hint нь ердөө dict эсвэл юу ч биш. TypedDict энэ бүтцийг тодорхойлдог:
python
from typing import Literal, TypedDict
Level = Literal["INFO", "WARNING", "ERROR"]
class LogRow(TypedDict):
date: str
time: str
level: Level
user: str
action: str
duration_ms: int
def parse_line(line: str) -> LogRow | None:
parts = line.split(" ", 3)
if len(parts) != 4:
return None
date, time, level, rest = parts
if level not in ("INFO", "WARNING", "ERROR"):
return None
fields = dict(pair.split("=", 1) for pair in rest.split() if "=" in pair)
return {
"date": date,
"time": time,
"level": level,
"user": fields.get("user", ""),
"action": fields.get("action", ""),
"duration_ms": int(fields.get("duration_ms", 0)),
}
row = parse_line("2026-10-09 08:15:40 ERROR user=saraa action=payment duration_ms=230")
print(row)
print(type(row)){'date': '2026-10-09', 'time': '08:15:40', 'level': 'ERROR', 'user': 'saraa', 'action': 'payment', 'duration_ms': 230}
<class 'dict'>LogRow class хэлбэрээр бичигдсэн ч энэ нь жинхэнэ class биш, маягт. Сүүлийн мөр үүнийг баталж байна: үр дүн энгийн dict. TypedDict ажиллах үед юу ч өөрчилдөггүй, dict-ийг шинэ объект болгон хувиргадаггүй. Тэр зөвхөн mypy-д "энэ dict эдгээр түлхүүрүүдтэй, тус бүр ийм төрлийн утгатай" гэж хэлдэг. Одоо row["duration_ms"] + 5-ийг mypy зөвшөөрнө, row["duraton_ms"] гэх үсгийн алдааг “LogRow-д ийм түлхүүр байхгүй” гэж анхааруулна, row["level"]-ийг Level гэж мэднэ. level талбарт Жишээ 2-ын Level-ийг хэрэглэсэн нь төрлүүд бие биенээ бүрдүүлж болдгийг харуулж байна.
Зарим түлхүүр заавал биш байж болно. Жишээлбэл, log мөр заримдаа “error_code” талбартай байж болно. Python 3.11-ээс хойш үүнийг NotRequired-ээр тэмдэглэнэ:
python
from typing import NotRequired, TypedDict
class LogRow(TypedDict):
date: str
time: str
level: Level
user: str
action: str
duration_ms: int
error_code: NotRequired[str]Одоо error_code-гүй dict ч LogRow-д тохирно, гэхдээ mypy row["error_code"]-д шууд хандахыг анхааруулж, row.get("error_code") эсвэл "error_code" in row шалгалт хийхийг шаардана. Шинэхэн Python 3.15-д TypedDict-д closed=True тохиргоо нэмэгдсэн: тэр маягтад заагаагүй илүү түлхүүрийг хориглодог. Энэ нь маш шинэ боломж тул бүх хэрэгсэл түүнийг хараахан дэмжээгүй байж магадгүй. Нэрийг нь мэдэж байхад хангалттай.
TypedDict уу, dataclass уу?
Хоёулаа "тодорхой талбаруудтай өгөгдөл"-ийг тодорхойлдог тул сонголт хийх хэрэгтэй. Шударга дүрэм: өгөгдөл аль хэдийн dict хэлбэрээр байгаа бөгөөд тэр хэлбэрээрээ үлдэх бол TypedDict. json.load()-ийн үр дүн, API-ийн хариу, Хичээл 9.3-ын csv.DictReader-ийн мөрүүд, бусад сангийн буцаадаг dict-ууд. Тэдгээрийг объект болгон хувиргахгүйгээр бүтцийг нь тодорхойлно. Ажиллах үеийн зардал огт байхгүй. Өгөгдөл таны програмын гол ойлголт бөгөөд түүнд зан чанар, шалгалт, method хэрэгтэй бол dataclass. Хичээл 7.4-ийн __post_init__, frozen, operator-ууд, bound method-ууд бүгд dataclass-д л боломжтой. Түгээмэл загвар нь хоёуланг нь хослуулах: гаднаас TypedDict хэлбэрийн dict хүлээн авч, шалгаад, програмын дотор dataclass объект болгох: Хичээл 7.1-ийн from_dict яг энэ хил.
Хүсвэл Хичээл 9.4-ийн events.py-ийн subscribe ба publish-д EventName Literal hint нэмж, VS Code дээр shop.py-д “order_place” гэж санаатайгаар бичээд, засварлагч юу харуулахыг ажиглаарай. Сонирхвол Хичээл 9.3-ын loader-уудын load() method-уудад Iterator[SaleRow] гэсэн буцаах hint нэмж болно: SaleRow бол product ба amount талбартай TypedDict, Iterator-ийг collections.abc-аас импортлоно. Мөн get_args()-ийг Union дээр туршиж, get_args(int | str) юу буцаахыг харах нь hint-үүд Python-д энгийн объект болохыг ойлгох сонирхолтой туршилт. Эдгээр бүгд санал бөгөөд дараагийн хичээлүүд үүнийг хийсэн гэж үзэхгүй.
Буруу төрлийн утга чимээгүй өнгөрөх. Мессежгүй, энэ хичээлийн гол сургамж. Hint бичсэн нь ажиллах үед хамгаалалт өгдөг гэж бодох нь буруу: Анхны жишээний total_price("1500", 3) ямар ч алдаагүй ажилласан. Hint-ийн хамгаалалт mypy эсвэл засварлагчаас ирнэ. Гаднаас ирэх өгөгдөлд, файл, API, хэрэглэгчийн оролт, ажиллах үеийн шалгалт заавал хэрэгтэй: isinstance, __post_init__, Жишээ 2-ын get_args загвар.
TypeError: Subscripted generics cannot be used with class and instance checks. "Дэд параметртэй generic төрлийг class ба instance шалгалтад хэрэглэх боломжгүй" гэсэн утгатай. Literal эсвэл list[str] зэрэг нарийн төрлийг isinstance-д өгөхөд гарна: isinstance(level, Level). isinstance зөвхөн жинхэнэ class-уудыг шалгаж чаддаг. Literal-д get_args загварыг хэрэглэ: level in get_args(Level). Union-ийн хувьд Python 3.10-аас хойш isinstance(value, int | str) ажилладаг гэдгийг анхаар: энгийн class-уудын union зөвшөөрөгддөг.
TypeError: TypedDict does not support instance and class checks. “TypedDict instance ба class шалгалтыг дэмждэггүй” гэсэн утгатай. isinstance(row, LogRow) гэж бичихэд гарна. Ажиллах үед LogRow-ийн объект гэж байхгүй, row бол энгийн dict. Хэрэв dict зөв түлхүүрүүдтэй эсэхийг ажиллах үед шалгах шаардлагатай бол түлхүүрүүдийг шууд шалгах, эсвэл dict-ийг шалгалттай dataclass руу хувиргах хэрэгтэй.
TypeError: unsupported operand type(s) for |: ‘str’ and ‘NoneType’. “| үйлдэлд дэмжигдэхгүй төрлүүд: ‘str’ ба ‘NoneType’” гэсэн утгатай. Хараахан тодорхойлогдоогүй class-ийг string-ээр бичээд | хэрэглэхэд type alias мөрөнд гарна: MaybeStudent = "Student" | None. Энд “Student” бол жинхэнэ class биш, string тул | тэр хоёрыг холбож чадахгүй. Python 3.14-өөс хойш функцийн гарын үсэг доторх hint-үүдийг Python шууд бодохгүй, хэрэгтэй үед л боддог тул тэнд асуудал гарахгүй. Харин энгийн оноолт шууд бодогддог. Шийдэл нь alias-ийг class тодорхойлогдсоны дараа бичих, эсвэл Python 3.12+-ийн type MaybeStudent = Student | None бичлэгийг ашиглах, тэр бичлэг ч мөн хойшлуулан бодогддог.
TypeError: unsupported operand type(s) for |: ‘type’ and ‘type’. Хэрэв таны компьютер дээр Python 3.9 эсвэл түүнээс хуучин хувилбар байгаа бол int | str бичлэг энэ алдааг өгнө. Энэ сургалт Python 3.12 эсвэл түүнээс шинэ хувилбарыг санал болгодог. Хувилбараа python --version-оор шалгаарай.
Python type hint-ийг ажиллах үед шалгадаггүй: hint-үүд функцийн __annotations__-д хадгалагдаж, засварлагч, mypy, FastAPI зэрэг хэрэгслүүдэд үйлчилдэг. Union, одоо int | str гэж бичигддэг, "эдгээр төрлийн аль нэг"-ийг илэрхийлдэг бөгөөд функц тохиолдол бүрийг isinstance эсвэл is None шалгалтаар нарийсгаж шийдэх ёстой. X | None нь "байж магадгүй утга"-ын стандарт хэлбэр. Literal "яг эдгээр утгуудын аль нэг"-ийг илэрхийлж, үйл явдлын нэр, log түвшин зэрэг хаалттай жагсаалтад тохирно. get_args() Literal-аас ажиллах үеийн жагсаалтыг гаргаж, давхардлыг арилгадаг. Type alias нь урт төрлийг нэрээр нь хэрэглэх боломж олгоно, Python 3.12+-д type бичлэгтэй. TypedDict dict-ийн түлхүүр ба утгын төрлүүдийг тодорхойлдог боловч ажиллах үед энгийн dict хэвээр. NotRequired заавал биш түлхүүрийг тэмдэглэнэ. Гаднаас ирэх dict-д TypedDict, програмын гол, зан чанартай ойлголтод dataclass. Hint ба ажиллах үеийн шалгалт бие биенээ орлохгүй, нөхдөг.
Энэ хичээлийн бүх төрөл тогтмол байсан: int, str, Student, тодорхой string-ууд. Харин зарим код ямар ч төрөлтэй ажиллах ёстой, гэхдээ тогтвортойгоор. Хичээл 8.2-ын Playlist-ийг ерөнхийлөөд дуу биш, ямар ч зүйл агуулдаг Stack бүтээе гэж бодъё. Хэрэв бид int хийвэл int гаргаж авах ёстой, string хийвэл string. Үүнийг hint-ээр хэрхэн илэрхийлэх вэ? list[Any] бол "юу ч" гэсэн утгатай, тогтвортой байдлыг алдана. Дараагийн хичээлд Generics ба TypeVar-тай танилцаж, "юу оруулна, тэр л гарна" гэсэн амлалтыг төрлийн хэлээр бичиж сурна.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.