Ачааллж байна...
Түвшин 3 дуусч байна. Та Бүлэг 7-д inheritance, Бүлэг 8-д special methods, Бүлэг 9-д dataclass үзсэн. Одоо тэдгээрийг НЭГ БҮРЭН ПРОГРАММ дээр ажиллуулах цаг болжээ.
Бид номын сангийн систем бичнэ. Тэр нь:
Ном, сэтгүүл, DVD -г бүртгэнэ (inheritance)
Гишүүд бүртгэнэ (dataclass)
Зүйл зээлэх, буцаах (төлөв удирдах)
Хайх, жагсаах, тайлагнах
Бүх өгөгдлийг JSON файлд хадгална — программ хаагдсаны дараа ч үлдэнэ
Энэ бол Бүлэг 6.5-ийн зарлагын бүртгэлээс ХАМААГҮЙ илүү нарийн систем. Тэнд бид нэг л төрлийн өгөгдөлтэй ажилласан. Энд гурван төрлийн зүйл, гишүүд, зээлийн харилцаа — бүгд хоорондоо холбогдоно.
Гэвч бүү айгаарай. Бид түүнийг хэсэг хэсгээр нь, алхам алхмаар барина.
Энэ бол гэрийн даалгавар БИШ. Бид хамтдаа бичнэ.
Алхам 1. Шинэ хавтас үүсгэнэ үү: nomiin-san.
Алхам 2. PowerShell нээж, venv үүсгээд идэвхжүүлнэ:
powershell
python -m venv venv
.\venv\Scripts\Activate.ps1(venv) гарч ирснийг баталгаажуулна уу. Гадны сан хэрэггүй.
Алхам 3. main.py нэртэй хоосон файл үүсгэнэ үү.
Код бичихээсээ өмнө бүтцээ шийдье. Бүлэг 7.4-ийн "бол" тестийг ашиглана.
Асуулт 1: Nom, Setguul, DVD — inheritance үү?
"Ном бол зүйл" → тийм, байгалийн ангилал
Гурвуулаа zeeldekh(), butsaakh() -ыг ашиглана → тийм
list[Zuil] үүсгэж, ижил байдлаар боловсруулна → тийм (polymorphism хэрэгтэй)
Хоёр түвшин л → тийм
Дүгнэлт: Zuil эцэг class, гурван хүү class. INHERITANCE.
Асуулт 2: Gishuun — Zuil -ээс өвлөх үү?
"Гишүүн бол зүйл" → ҮГҮЙ! Гишүүн бол хүн, зээлж болох эд БИШ.
Дүгнэлт: тусдаа class, ямар ч inheritance байхгүй. DATACLASS.
Асуулт 3: NomiinSan — юугаас өвлөх вэ?
"Номын сан бол ном" → ҮГҮЙ. Утгагүй.
Номын санд номнууд, гишүүд БАЙДАГ → COMPOSITION
Дүгнэлт: NomiinSan нь list[Zuil] ба list[Gishuun] -ыг ХАДГАЛНА.
Эцсийн бүтэц:
Zuil (эцэг class — inheritance)
├── Nom
├── Setguul
└── DVD
Gishuun (dataclass — тусдаа)
NomiinSan (composition)
├── zuiluud: list[Zuil]
└── gishuud: list[Gishuun]Асуулт 4: Zuil -ыг dataclass болгох уу?
Энэ нь илүү нарийн асуулт. Zuil нь ТӨЛӨВТЭЙ (zeeldsen нь өөрчлөгддөг), мөн inheritance-тэй. Dataclass нь inheritance-тэй ажиллах ч гэсэн, super().__init__() -ийн бичлэг нь төвөгтэй болдог.
Шийдвэр: Zuil ба түүний хүү class-уудыг ЕРДИЙН class болгоно (Бүлэг 7-8-ын мэдлэгээр). Gishuun -ыг DATACLASS болгоно (тэр нь энгийн өгөгдөл).
Ингэснээр та ХОЁУЛАНГ НЬ нэг программд ашиглаж, ялгааг мэдрэх болно.
python
# main.py
# Номын сангийн систем — Түвшин 3 guided build
import json
from dataclasses import dataclass, asdict, field
ZAM = "nomiin_san.json"
class Zuil:
def __init__(self, id: str, garchig: str, on: int) -> None:
self.id = id
self.garchig = garchig
self.on = on
self.zeeldsen_gishuun: int | None = None
@property
def zeeldsen(self) -> bool:
return self.zeeldsen_gishuun is not None
@property
def baidal(self) -> str:
return "зээлсэн" if self.zeeldsen else "боломжтой"
@property
def turul(self) -> str:
return "зүйл"
def zeeleh_khugatsaa(self) -> int:
return 14
def zeeldekh_bolokh_uu(self) -> bool:
return not self.zeeldsen
def zeeldekh(self, gishuunii_id: int) -> bool:
if not self.zeeldekh_bolokh_uu():
return False
self.zeeldsen_gishuun = gishuunii_id
return True
def butsaakh(self) -> None:
self.zeeldsen_gishuun = None
def __eq__(self, busad: object) -> bool:
if not isinstance(busad, Zuil):
return NotImplemented
return self.id == busad.id
def __hash__(self) -> int:
return hash(self.id)
def __str__(self) -> str:
return f"[{self.id}] {self.garchig} ({self.on}) — {self.baidal}"
def __repr__(self) -> str:
return f"{type(self).__name__}(id={self.id!r}, garchig={self.garchig!r}, on={self.on})"
def to_dict(self) -> dict:
return {
"turul": type(self).__name__,
"id": self.id,
"garchig": self.garchig,
"on": self.on,
"zeeldsen_gishuun": self.zeeldsen_gishuun,
}Хэдэн зүйлийг тайлбарлая.
self.zeeldsen_gishuun: int | None = None — энэ бол ухаалаг дизайн. Бид зөвхөн "зээлэгдсэн үү" гэсэн bool хадгалахын оронд, ХЭН зээлсэн бэ гэдгийг хадгалж байна.
None бол "хэн ч зээлээгүй" (Бүлэг 4.3). Тоо бол "тэр гишүүн зээлсэн."
Нэг талбар, хоёр мэдээлэл. Илүү сайн.
@property zeeldsen (Бүлэг 8.4) — тооцоологдсон утга. zeeldsen_gishuun is not None гэж шалгаж байна. Хадгалагдаагүй тул ХЭЗЭЭ Ч хуучрахгүй.
Мөн setter байхгүй тул zuil.zeeldsen = True гэж бичих БОЛОМЖГҮЙ — дүрмийг тойрч гарах хаалга хаагдсан.
@property turul — хүү class-ууд түүнийг override хийнэ.
__eq__ ба __hash__ (Бүлэг 8.2) — зөвхөн id -ээр харьцуулж байна. Учир нь id нь ОНОШ. Гарчиг өөрчлөгдсөн ч, зээлэгдсэн ч — тэр нь ТЭР ЗҮЙЛ хэвээрээ.
type(self).__name__ — объектын class-ын нэрийг string болгож өгдөг. Nom объект дээр "Nom" буцаана. Тэр нь __repr__ ба to_dict -д хэрэгтэй.
to_dict нь "turul" талбарыг оруулж байгааг анзаараарай. Яагаад? Учир нь JSON-оос буцааж уншихад бид ЯМАР class үүсгэхээ мэдэх ёстой. JSON нь class-ыг мэдэхгүй (Бүлэг 6.1) тул бид түүнийг ГАРААР тэмдэглэж байна.
python
class Nom(Zuil):
def __init__(
self, id: str, garchig: str, on: int, zokhiolch: str, khuudas: int
) -> None:
super().__init__(id, garchig, on)
self.zokhiolch = zokhiolch
self.khuudas = khuudas
@property
def turul(self) -> str:
return "ном"
def __str__(self) -> str:
return f"{super().__str__()} | {self.zokhiolch}, {self.khuudas} хуудас"
def to_dict(self) -> dict:
d = super().to_dict()
d["zokhiolch"] = self.zokhiolch
d["khuudas"] = self.khuudas
return d
class Setguul(Zuil):
def __init__(self, id: str, garchig: str, on: int, dugaar: int) -> None:
super().__init__(id, garchig, on)
self.dugaar = dugaar
@property
def turul(self) -> str:
return "сэтгүүл"
def zeeleh_khugatsaa(self) -> int:
return 7
def __str__(self) -> str:
return f"{super().__str__()} | №{self.dugaar}"
def to_dict(self) -> dict:
d = super().to_dict()
d["dugaar"] = self.dugaar
return d
class DVD(Zuil):
def __init__(self, id: str, garchig: str, on: int, minut: int) -> None:
super().__init__(id, garchig, on)
self.minut = minut
@property
def turul(self) -> str:
return "DVD"
def zeeleh_khugatsaa(self) -> int:
return 3
def __str__(self) -> str:
return f"{super().__str__()} | {self.minut} минут"
def to_dict(self) -> dict:
d = super().to_dict()
d["minut"] = self.minut
return dБүлэг 7-ийн БҮХ мэдлэг ажиллаж байна.
super().__init__(id, garchig, on) (Бүлэг 7.2) — эцгийн __init__ -ыг ЭХЛЭЭД дуудна.
turul, zeeleh_khugatsaa, __str__, to_dict — бүгд override (Бүлэг 7.3).
super().__str__() ба super().to_dict() — эцгийнхийг ДУУДАЖ, түүн дээр НЭМЖ байна. "Солих биш, өргөтгөх."
to_dict -ийн хэв маягийг анхааралтай хараарай:
python
def to_dict(self) -> dict:
d = super().to_dict() # эцгийн бүх талбар
d["zokhiolch"] = self.zokhiolch # өөрийн талбар нэмнэ
d["khuudas"] = self.khuudas
return dДавхардал байхгүй. id, garchig, on, zeeldsen_gishuun -ыг гурван удаа бичээгүй.
Зээлэх хугацаа өөр өөр: ном 14 хоног, сэтгүүл 7, DVD 3. Бүлэг 7.3-ын "зан төлөвийг өөрчлөх" override.
python
@dataclass
class Gishuun:
id: int
ner: str
utas: str = ""
zeeldsen_zuiluud: list[str] = field(default_factory=list)
def __post_init__(self) -> None:
if not self.ner.strip():
raise ValueError("Гишүүний нэр хоосон байж болохгүй")
if self.id <= 0:
raise ValueError(f"ID эерэг байх ёстой: {self.id}")
@property
def zeeldsen_too(self) -> int:
return len(self.zeeldsen_zuiluud)
def __str__(self) -> str:
return f"[{self.id}] {self.ner} — {self.zeeldsen_too} зүйл зээлсэн"
def to_dict(self) -> dict:
return asdict(self)
@classmethod
def from_dict(cls, d: dict) -> "Gishuun":
return cls(**d)Бүлэг 9-ийн БҮХ мэдлэг ажиллаж байна.
@dataclass — __init__, __repr__, __eq__ автоматаар (Бүлэг 9.1).
utas: str = "" — default утга (Бүлэг 9.2).
field(default_factory=list) — mutable default -ын урхийг зайлсхийж байна (Бүлэг 9.2). Гишүүн бүр ӨӨРИЙН жагсаалттай.
__post_init__ — шалгалт. Хүчингүй гишүүн ОГТ үүсэхгүй (Бүлэг 9.2).
@property zeeldsen_too — тооцоологдсон, үргэлж шинэхэн (Бүлэг 8.4).
to_dict / from_dict — JSON гүүр (Бүлэг 9.3).
zeeldsen_zuiluud нь зүйлийн ID -үүдийн жагсаалт гэдгийг анзаараарай, объектуудын жагсаалт БИШ.
Яагаад? Учир нь JSON-д объект хадгалж болохгүй. Мөн энэ нь давхардлаас сэргийлнэ — зүйл нь НЭГ л газарт (zuiluud жагсаалтад) амьдарна, бусад газарт зөвхөн ID -ээр лавлагдана.
Энэ бол өгөгдлийн сангийн үндсэн зарчим: өгөгдлийг давхардуулж болохгүй, зөвхөн лавлагаа хадгал.
python
class NomiinSan:
def __init__(self, ner: str) -> None:
self.ner = ner
self.zuiluud: list[Zuil] = []
self.gishuud: list[Gishuun] = []
# ─── Нэмэх ───────────────────────────────────────────────
def zuil_nemekh(self, zuil: Zuil) -> bool:
if self.zuil_khaikh(zuil.id) is not None:
print(f"Алдаа: {zuil.id} ID -тай зүйл аль хэдийн байна.")
return False
self.zuiluud.append(zuil)
return True
def gishuun_nemekh(self, gishuun: Gishuun) -> bool:
if self.gishuun_khaikh(gishuun.id) is not None:
print(f"Алдаа: {gishuun.id} ID -тай гишүүн аль хэдийн байна.")
return False
self.gishuud.append(gishuun)
return True
# ─── Хайх ────────────────────────────────────────────────
def zuil_khaikh(self, id: str) -> Zuil | None:
for z in self.zuiluud:
if z.id == id:
return z
return None
def gishuun_khaikh(self, id: int) -> Gishuun | None:
for g in self.gishuud:
if g.id == id:
return g
return None
def garchgaar_khaikh(self, tekst: str) -> list[Zuil]:
tekst_tsever = tekst.strip().lower()
return [z for z in self.zuiluud if tekst_tsever in z.garchig.lower()]
# ─── Зээл ────────────────────────────────────────────────
def zeeldekh(self, zuiliin_id: str, gishuunii_id: int) -> bool:
zuil = self.zuil_khaikh(zuiliin_id)
if zuil is None:
print(f"Алдаа: '{zuiliin_id}' ID -тай зүйл олдсонгүй.")
return False
gishuun = self.gishuun_khaikh(gishuunii_id)
if gishuun is None:
print(f"Алдаа: {gishuunii_id} ID -тай гишүүн олдсонгүй.")
return False
if not zuil.zeeldekh(gishuunii_id):
print(f"Алдаа: '{zuil.garchig}' аль хэдийн зээлэгдсэн байна.")
return False
gishuun.zeeldsen_zuiluud.append(zuiliin_id)
print(
f"{gishuun.ner} — '{zuil.garchig}' -ыг {zuil.zeeleh_khugatsaa()} "
f"хоногоор зээллээ."
)
return True
def butsaakh(self, zuiliin_id: str) -> bool:
zuil = self.zuil_khaikh(zuiliin_id)
if zuil is None:
print(f"Алдаа: '{zuiliin_id}' ID -тай зүйл олдсонгүй.")
return False
if not zuil.zeeldsen:
print(f"Алдаа: '{zuil.garchig}' зээлэгдээгүй байна.")
return False
gishuunii_id = zuil.zeeldsen_gishuun
gishuun = self.gishuun_khaikh(gishuunii_id) if gishuunii_id else None
zuil.butsaakh()
if gishuun is not None and zuiliin_id in gishuun.zeeldsen_zuiluud:
gishuun.zeeldsen_zuiluud.remove(zuiliin_id)
print(f"'{zuil.garchig}' буцаагдлаа.")
return True
# ─── Тайлан ──────────────────────────────────────────────
@property
def zeeldsen_too(self) -> int:
return sum(1 for z in self.zuiluud if z.zeeldsen)
@property
def bolomjtoi_too(self) -> int:
return len(self.zuiluud) - self.zeeldsen_too
def __len__(self) -> int:
return len(self.zuiluud)
def __getitem__(self, index: int) -> Zuil:
return self.zuiluud[index]
def __str__(self) -> str:
return (
f"{self.ner}: {len(self)} зүйл "
f"({self.bolomjtoi_too} боломжтой, {self.zeeldsen_too} зээлсэн), "
f"{len(self.gishuud)} гишүүн"
)Бүлэг 7.4 ба 8.3-ын мэдлэг ажиллаж байна.
Composition: self.zuiluud ба self.gishuud — номын сан нь тэднийг ХАДГАЛЖ байна, өвлөөгүй.
__len__ ба __getitem__ (Бүлэг 8.3) — эдгээр НЭГ ХОСООС ес чадвар ирнэ:
python
len(san)
if san:
san[0]
san[:3]
for z in san:
[z for z in san if ...]
sorted(san, key=...)zeeldekh -ийн логикийг анхааралтай хараарай. Тэнд ГУРВАН шалгалт байна:
Зүйл байна уу?
Гишүүн байна уу?
Зүйл зээлэгдэх боломжтой юу?
Гурвуулаа "эрт буцах" хэв маягаар (Бүлэг 4.3). Код нь хавтгай, уншихад амархан.
Мөн zeeldekh нь ХОЁР талыг ЗЭРЭГ шинэчилж байна: зүйлийн zeeldsen_gishuun ба гишүүний zeeldsen_zuiluud. Хоёулаа зөв байх ЁСТОЙ, эс тэгвээс өгөгдөл зөрчилдөнө.
Одоо хамгийн нарийн хэсэг. Бид ГУРВАН өөр class-ын объектуудыг нэг файлд хадгалж, буцааж унших ёстой.
python
# ─── JSON ────────────────────────────────────────────────
def khadgalakh(self, zam: str) -> None:
uguudul = {
"ner": self.ner,
"zuiluud": [z.to_dict() for z in self.zuiluud],
"gishuud": [g.to_dict() for g in self.gishuud],
}
with open(zam, "w", encoding="utf-8") as f:
json.dump(uguudul, f, ensure_ascii=False, indent=2)
def zuil_uusgekh(d: dict) -> Zuil | None:
turul = d.get("turul")
zeeldsen_gishuun = d.get("zeeldsen_gishuun")
try:
if turul == "Nom":
zuil: Zuil = Nom(
id=d["id"],
garchig=d["garchig"],
on=d["on"],
zokhiolch=d["zokhiolch"],
khuudas=d["khuudas"],
)
elif turul == "Setguul":
zuil = Setguul(
id=d["id"],
garchig=d["garchig"],
on=d["on"],
dugaar=d["dugaar"],
)
elif turul == "DVD":
zuil = DVD(
id=d["id"],
garchig=d["garchig"],
on=d["on"],
minut=d["minut"],
)
else:
print(f"Анхаар: тодорхойгүй төрөл '{turul}' — алгаслаа.")
return None
except KeyError as alda:
print(f"Анхаар: талбар дутуу байна ({alda}) — алгаслаа.")
return None
zuil.zeeldsen_gishuun = zeeldsen_gishuun
return zuil
def san_achaalakh(zam: str, undsen_ner: str = "Номын сан") -> NomiinSan:
try:
with open(zam, "r", encoding="utf-8") as f:
uguudul = json.load(f)
except FileNotFoundError:
return NomiinSan(undsen_ner)
except json.JSONDecodeError as alda:
print(f"Файл эвдэрсэн: {alda}")
return NomiinSan(undsen_ner)
san = NomiinSan(uguudul.get("ner", undsen_ner))
for d in uguudul.get("zuiluud", []):
zuil = zuil_uusgekh(d)
if zuil is not None:
san.zuiluud.append(zuil)
for d in uguudul.get("gishuud", []):
try:
san.gishuud.append(Gishuun.from_dict(d))
except (TypeError, ValueError) as alda:
print(f"Анхаар: гишүүн алгаслаа ({alda})")
return sanХамгийн чухал хэсгийг тайлбарлая.
zuil_uusgekh(d) — энэ бол ГҮҮР. Тэр нь dictionary-ээс ЗӨВ class-ыг сэргээж байна.
Яагаад Zuil(**d) гэж шууд бичиж болохгүй вэ? Учир нь JSON нь class-ыг мэдэхгүй (Бүлэг 6.1). Файлаас уншсан dictionary нь зүгээр л dictionary — тэр нь Nom уу, DVD юу гэдгийг мэдэхгүй.
Тиймээс бид to_dict -д "turul" талбарыг ЗОРИУДААР оруулсан. Одоо zuil_uusgekh нь түүнийг уншиж, зөв class-ыг сонгож байна.
Энэ хэв маягийг "factory" гэж нэрлэдэг (үйлдвэр). Тэр нь "өгөгдлөөс хамааран зөв төрлийн объект үүсгэдэг функц" гэсэн утгатай.
except KeyError — хэрэв JSON-д талбар дутуу байвал (d["zokhiolch"] олдохгүй) — бичлэгийг алгасна, программ унахгүй.
zuil.zeeldsen_gishuun = zeeldsen_gishuun — зээлийн төлөвийг СЭРГЭЭЖ байна. Тэр нь __init__ -д байхгүй тул тусад нь тавих хэрэгтэй.
Gishuun.from_dict(d) — dataclass нь **d -ээр амархан сэргэдэг (Бүлэг 9.3), учир нь inheritance байхгүй.
python
def tsees_khevlekh() -> None:
print()
print("=" * 60)
print("НОМЫН САНГИЙН СИСТЕМ")
print("=" * 60)
print("1. Зүйл нэмэх")
print("2. Гишүүн нэмэх")
print("3. Бүх зүйл харах")
print("4. Бүх гишүүн харах")
print("5. Зээлэх")
print("6. Буцаах")
print("7. Хайх")
print("8. Тайлан")
print("0. Гарах")
print("=" * 60)
def zuil_nemekh_asuukh(san: NomiinSan) -> None:
print()
print("Төрөл: 1) Ном 2) Сэтгүүл 3) DVD")
turul = input("Сонголт: ").strip()
id = input("ID (жишээ: N001): ").strip()
if not id:
print("Алдаа: ID хоосон байж болохгүй.")
return
garchig = input("Гарчиг: ").strip()
if not garchig:
print("Алдаа: гарчиг хоосон байж болохгүй.")
return
try:
on = int(input("Он: ").strip())
except ValueError:
print("Алдаа: он нь тоо байх ёстой.")
return
try:
if turul == "1":
zokhiolch = input("Зохиолч: ").strip()
khuudas = int(input("Хуудасны тоо: ").strip())
zuil: Zuil = Nom(id, garchig, on, zokhiolch, khuudas)
elif turul == "2":
dugaar = int(input("Дугаар: ").strip())
zuil = Setguul(id, garchig, on, dugaar)
elif turul == "3":
minut = int(input("Үргэлжлэх хугацаа (минут): ").strip())
zuil = DVD(id, garchig, on, minut)
else:
print("Алдаа: буруу төрөл.")
return
except ValueError:
print("Алдаа: тоон утга буруу байна.")
return
if san.zuil_nemekh(zuil):
san.khadgalakh(ZAM)
print(f"Нэмлээ: {zuil}")
def gishuun_nemekh_asuukh(san: NomiinSan) -> None:
print()
try:
id = int(input("Гишүүний ID (тоо): ").strip())
except ValueError:
print("Алдаа: ID нь тоо байх ёстой.")
return
ner = input("Нэр: ").strip()
utas = input("Утас (заавал биш): ").strip()
try:
gishuun = Gishuun(id=id, ner=ner, utas=utas)
except ValueError as alda:
print(f"Алдаа: {alda}")
return
if san.gishuun_nemekh(gishuun):
san.khadgalakh(ZAM)
print(f"Нэмлээ: {gishuun}")
def zuiluud_jagsaakh(san: NomiinSan) -> None:
if not san:
print("\nЗүйл байхгүй байна.")
return
print()
print("=" * 60)
for zuil in sorted(san, key=lambda z: z.id):
print(zuil)
print("=" * 60)
print(san)
def gishuud_jagsaakh(san: NomiinSan) -> None:
if not san.gishuud:
print("\nГишүүн байхгүй байна.")
return
print()
print("=" * 60)
for g in sorted(san.gishuud, key=lambda g: g.id):
print(g)
for zuiliin_id in g.zeeldsen_zuiluud:
zuil = san.zuil_khaikh(zuiliin_id)
if zuil is not None:
print(f" → {zuil.garchig}")
print("=" * 60)
def zeeldekh_asuukh(san: NomiinSan) -> None:
print()
zuiliin_id = input("Зүйлийн ID: ").strip()
try:
gishuunii_id = int(input("Гишүүний ID: ").strip())
except ValueError:
print("Алдаа: гишүүний ID нь тоо байх ёстой.")
return
if san.zeeldekh(zuiliin_id, gishuunii_id):
san.khadgalakh(ZAM)
def butsaakh_asuukh(san: NomiinSan) -> None:
print()
zuiliin_id = input("Буцаах зүйлийн ID: ").strip()
if san.butsaakh(zuiliin_id):
san.khadgalakh(ZAM)
def khaikh_asuukh(san: NomiinSan) -> None:
print()
tekst = input("Хайх текст: ").strip()
if not tekst:
print("Алдаа: хайх текст хоосон байна.")
return
oldson = san.garchgaar_khaikh(tekst)
if not oldson:
print(f"'{tekst}' -тай тохирох зүйл олдсонгүй.")
return
print()
print(f"{len(oldson)} зүйл олдлоо:")
for zuil in oldson:
print(f" {zuil}")
def tailan_khevlekh(san: NomiinSan) -> None:
if not san:
print("\nЗүйл байхгүй байна.")
return
turlээr: dict[str, int] = {}
for zuil in san:
turlээr[zuil.turul] = turlээr.get(zuil.turul, 0) + 1
print()
print("=" * 60)
print("ТАЙЛАН")
print("=" * 60)
print(f"Номын сан: {san.ner}")
print(f"Нийт зүйл: {len(san)}")
print(f"Боломжтой: {san.bolomjtoi_too}")
print(f"Зээлсэн: {san.zeeldsen_too}")
print(f"Гишүүд: {len(san.gishuud)}")
print()
print("ТӨРЛӨӨР")
print("-" * 60)
for turul, too in sorted(turlээr.items(), key=lambda h: h[1], reverse=True):
print(f" {turul:<14} {too:>4}")
print()
zeeldsen = [z for z in san if z.zeeldsen]
if zeeldsen:
print("ЗЭЭЛСЭН ЗҮЙЛС")
print("-" * 60)
for zuil in zeeldsen:
gishuun = san.gishuun_khaikh(zuil.zeeldsen_gishuun or 0)
gishuunii_ner = gishuun.ner if gishuun else "тодорхойгүй"
print(f" {zuil.garchig:<28} → {gishuunii_ner}")
print("=" * 60)
def gol_loop() -> None:
san = san_achaalakh(ZAM, "Улаанбаатарын номын сан")
print(f"\nАчаалсан: {san}")
while True:
tsees_khevlekh()
songolt = input("Сонголт: ").strip()
if songolt == "1":
zuil_nemekh_asuukh(san)
elif songolt == "2":
gishuun_nemekh_asuukh(san)
elif songolt == "3":
zuiluud_jagsaakh(san)
elif songolt == "4":
gishuud_jagsaakh(san)
elif songolt == "5":
zeeldekh_asuukh(san)
elif songolt == "6":
butsaakh_asuukh(san)
elif songolt == "7":
khaikh_asuukh(san)
elif songolt == "8":
tailan_khevlekh(san)
elif songolt == "0":
san.khadgalakh(ZAM)
print("\nХадгалж, гарлаа. Баяртай.")
break
else:
print("\nБуруу сонголт.")
gol_loop()Гурван давхарга дахин ажиллаж байна (Бүлэг 6.5-ийн хэв маяг):
ЛОГИК — NomiinSan -ийн method-ууд, input() мэдэхгүй
ХАРУУЛАХ — zuiluud_jagsaakh, tailan_khevlekh
ИНТЕРФЕЙС — *_asuukh функцууд, хэрэглэгчээс асууна
Бүлэг 12-т энэ салгалт нь ФАЙЛЫН хуваарь болно.
powershell
python main.pyХэдэн зүйл нэмээд, гишүүн нэмээд, зээлээд, тайлан харъя:
Ачаалсан: Улаанбаатарын номын сан: 0 зүйл (0 боломжтой, 0 зээлсэн), 0 гишүүн
============================================================
НОМЫН САНГИЙН СИСТЕМ
============================================================
1. Зүйл нэмэх
...
Сонголт: 1
Төрөл: 1) Ном 2) Сэтгүүл 3) DVD
Сонголт: 1
ID (жишээ: N001): N001
Гарчиг: Монголын нууц товчоо
Он: 1240
Зохиолч: Тодорхойгүй
Хуудасны тоо: 320
Нэмлээ: [N001] Монголын нууц товчоо (1240) — боломжтой | Тодорхойгүй, 320 хуудасХэдэн зүйл нэмсний дараа:
Сонголт: 3
============================================================
[D001] Аав (2019) — боломжтой | 95 минут
[N001] Монголын нууц товчоо (1240) — зээлсэн | Тодорхойгүй, 320 хуудас
[N002] Цагаан хэрэм (1971) — боломжтой | Б. Ринчен, 450 хуудас
[S001] Шинжлэх ухаан (2026) — боломжтой | №7
============================================================
Улаанбаатарын номын сан: 4 зүйл (3 боломжтой, 1 зээлсэн), 2 гишүүнPolymorphism ажиллаж байна. Loop нь print(zuil) гэж НЭГ л удаа бичсэн, гэвч ном, сэтгүүл, DVD бүр өөр өөр мэдээлэл харуулж байна.
Тайлан:
Сонголт: 8
============================================================
ТАЙЛАН
============================================================
Номын сан: Улаанбаатарын номын сан
Нийт зүйл: 4
Боломжтой: 3
Зээлсэн: 1
Гишүүд: 2
ТӨРЛӨӨР
------------------------------------------------------------
ном 2
DVD 1
сэтгүүл 1
ЗЭЭЛСЭН ЗҮЙЛС
------------------------------------------------------------
Монголын нууц товчоо → Болд
============================================================Одоо гараад (0), ДАХИН ажиллуулаарай:
Ачаалсан: Улаанбаатарын номын сан: 4 зүйл (3 боломжтой, 1 зээлсэн), 2 гишүүнБҮХ ЗҮЙЛ ҮЛДЛЭЭ — зээлийн төлөв ч бас. nomiin_san.json файлыг нээж хараарай:
json
{
"ner": "Улаанбаатарын номын сан",
"zuiluud": [
{
"turul": "Nom",
"id": "N001",
"garchig": "Монголын нууц товчоо",
"on": 1240,
"zeeldsen_gishuun": 1001,
"zokhiolch": "Тодорхойгүй",
"khuudas": 320
},
{
"turul": "DVD",
"id": "D001",
"garchig": "Аав",
"on": 2019,
"zeeldsen_gishuun": null,
"minut": 95
}
],
"gishuud": [
{
"id": 1001,
"ner": "Болд",
"utas": "99112233",
"zeeldsen_zuiluud": ["N001"]
}
]
}"turul" талбар нь class-ыг тэмдэглэж байна. Тэр нь JSON-оос буцааж уншихад зөв class сэргээх түлхүүр юм.
"zeeldsen_gishuun": null — Бүлэг 6.1-ээс санаж байгаа байх: Python-ы None нь JSON-д null болдог.
Өргөтгөл 1: зээлийн огноо ба хугацаа хэтрэлт.
python
from datetime import date, timedelta
class Zuil:
def __init__(self, id: str, garchig: str, on: int) -> None:
...
self.zeeldsen_ognoo: str | None = None
def zeeldekh(self, gishuunii_id: int) -> bool:
if not self.zeeldekh_bolokh_uu():
return False
self.zeeldsen_gishuun = gishuunii_id
self.zeeldsen_ognoo = date.today().isoformat()
return True
@property
def khugatsaa_khetersen_uu(self) -> bool:
if self.zeeldsen_ognoo is None:
return False
zeeldsen = date.fromisoformat(self.zeeldsen_ognoo)
khugatsaa = zeeldsen + timedelta(days=self.zeeleh_khugatsaa())
return date.today() > khugatsaaӨргөтгөл 2: гишүүний зээлийн хязгаар.
python
@dataclass
class Gishuun:
...
KHYAZGAAR: int = 3 # тогтмол
@property
def zeelekh_bolokh_uu(self) -> bool:
return self.zeeldsen_too < 3Дараа нь NomiinSan.zeeldekh -д шалгалт нэмнэ.
Өргөтгөл 3: CSV экспорт (Бүлэг 6.3).
python
import csv
def csv_ruu_gargakh(san: NomiinSan, zam: str = "zuiluud.csv") -> None:
with open(zam, "w", encoding="utf-8-sig", newline="") as f:
bichigch = csv.DictWriter(
f, fieldnames=["id", "turul", "garchig", "on", "baidal"]
)
bichigch.writeheader()
for z in san:
bichigch.writerow({
"id": z.id,
"turul": z.turul,
"garchig": z.garchig,
"on": z.on,
"baidal": z.baidal,
})encoding="utf-8-sig" — Excel-д кирилл зөв харагдана.
1. "turul" талбарыг мартах
JSON-оос буцааж уншихад ямар class үүсгэхээ мэдэхгүй болно. Бүх зүйл Zuil болж, zokhiolch, minut зэрэг талбарууд алдагдана.
2. Хоёр талыг зэрэг шинэчлэхээ мартах
python
def zeeldekh(self, zuiliin_id: str, gishuunii_id: int) -> bool:
zuil.zeeldekh(gishuunii_id)
# gishuun.zeeldsen_zuiluud.append(zuiliin_id) ← МАРТСАН!Зүйл нь "зээлэгдсэн" гэж хэлнэ, гэвч гишүүний жагсаалтад байхгүй. Өгөгдөл зөрчилдлөө.
3. Хадгалахаа мартах
Бүлэг 6.5-ын сургамж хэвээрээ. Өөрчлөх бүрдээ san.khadgalakh(ZAM).
4. Mutable default
python
zeeldsen_zuiluud: list[str] = []ValueError: mutable default <class 'list'> ... use default_factoryfield(default_factory=list) (Бүлэг 9.2).
5. zeeldsen -д утга олгох гэж оролдох
python
zuil.zeeldsen = TrueAttributeError: property 'zeeldsen' of 'Zuil' object has no setterЭнэ бол ЗӨВ зан төлөв! Property нь дүрмийг тойрч гарах хаалгыг хаасан (Бүлэг 8.4). Зээлэхийн тулд zuil.zeeldekh(gishuunii_id) дуудна.
Түвшин 3-ын БҮХ мэдлэг нэг программд нийллээ.
Бүлэг 7 (inheritance): Zuil эцэг, Nom/Setguul/DVD хүү. super().__init__(), method override, polymorphism (for zuil in san: print(zuil)).
Бүлэг 7.4 (composition): NomiinSan нь зүйл, гишүүдийг ХАДГАЛНА, өвлөөгүй. "Номын сан бол ном" гэж хэлж болохгүй.
Бүлэг 8 (special methods): __str__ (гоё), __repr__ (техникийн), __eq__/__hash__ (зөвхөн id — ОНОШ), __len__/__getitem__ (ес чадвар), @property (тооцоологдсон, үргэлж шинэхэн).
Бүлэг 9 (dataclass): Gishuun — field(default_factory=list), __post_init__ шалгалт, from_dict/to_dict.
JSON гүүр: to_dict -д "turul" талбар ЗААВАЛ — JSON нь class-ыг мэдэхгүй тул түүнийг гараар тэмдэглэнэ. zuil_uusgekh нь түүнийг уншиж, зөв class-ыг сонгоно (factory хэв маяг).
Гурван давхарга: ЛОГИК (NomiinSan) → ХАРУУЛАХ → ИНТЕРФЕЙС. Бүлэг 12-т энэ нь файлын хуваарь болно.
Өгөгдлийг давхардуулж болохгүй: гишүүн нь зүйлийн ID -үүдийг хадгална, объектуудыг биш.
Зээлэхэд ХОЁР талыг зэрэг шинэчил: зүйлийн zeeldsen_gishuun ба гишүүний zeeldsen_zuiluud.
ТҮВШИН 3 ДУУСЛАА. Та одоо OOP-ыг жинхэнэ эзэмшлээ: inheritance-ыг зөв ашиглах, composition-ыг илүүд үзэх, special method-оор Python-той ярих, dataclass-аар boilerplate-ыг арилгах.
Түвшин 4-т таны программ анх удаа ГАДААД ЕРТӨНЦТЭЙ ярина.
Бүлэг 10 (Decorator): @ тэмдгийн ард юу байдгийг ЭЦЭСТ НЬ тайлна. Та @property, @dataclass, @classmethod -ыг ашигласан — одоо тэдгээр яаж ажилладгийг ойлгож, өөрөө decorator бичнэ. Дараа нь FastAPI-гийн @app.get("/") -ыг харахад тэр нь илбэ биш байх болно.
Бүлэг 11 (HTTP ба API): интернэт хэрхэн ажилладаг, API гэж юу вэ, requests санаар жинхэнэ өгөгдөл татах.
Бүлэг 12 (Олон файлтай төсөл): та энэ 400 мөрийн main.py -г олон файл болгож хуваахыг сурна — яг мэргэжлийн төсөл шиг.
Түвшин 4-ийн guided build нь жинхэнэ цаг агаарын API-аас өгөгдөл татдаг, олон файлтай программ байх болно.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.