Ачааллж байна...
Түвшин 2 дуусч байна. Та Бүлэг 4-т type hint, Бүлэг 5-д өгөгдлийн бүтцүүд, Бүлэг 6-д JSON, CSV, өгөгдөл цэвэрлэх — бүгдийг үзсэн. Одоо тэдгээрийг нэг бүрэн программ болгож нийлүүлэх цаг болжээ.
Бид зарлагын бүртгэлийн программ бичнэ. Тэр нь:
Зарлага нэмнэ (нэр, дүн, ангилал)
Бүх зарлагыг жагсаана
Нийт дүн, дундаж гаргана
Ангилалаар шүүнэ, ангилалаар бүлэглэж тайлагнана
Зарлага устгана
Бүх өгөгдлийг JSON файлд хадгална — программ хаагдсаны дараа ч үлдэнэ
Энэ бол Курс 1-ийн хийх ажлын жагсаалтын жинхэнэ өв залгамжлагч. Тэр программд та цэс үзүүлж, хэрэглэгчийн сонголтыг уншиж, жагсаалтад ажил нэмж байсан. Ижил санаа, ижил бүтэц. Гэхдээ гурван том ялгаа бий:
Бүрэн type hint-тэй — функц бүрийн гэрээ тодорхой
JSON файлд хадгалагдана — өгөгдөл алдагдахгүй
Цэвэрлэх давхаргатай — бохир оролт программыг унагахгүй
Тэр гурван ялгаа нь "сурах программ" ба "жинхэнэ программ" -ын хоорондох зааг юм.
Энэ бол гэрийн даалгавар БИШ. Бид түүнийг хамтдаа, хэсэг хэсгээр нь барина. Алхам бүрийн дараа ажиллуулж, юу харагдахыг хэлнэ.
Алхам 1. Шинэ хавтас үүсгэнэ үү, жишээ нь zarlagiin-burtgel.
Алхам 2. PowerShell нээж, venv үүсгээд идэвхжүүлнэ:
powershell
python -m venv venv
.\venv\Scripts\Activate.ps1(venv) гарч ирснийг баталгаажуулна уу. Гадны сан хэрэггүй — json нь Python-той хамт ирдэг.
Алхам 3. main.py нэртэй хоосон файл үүсгэнэ үү.
Хавтасны бүтэц:
zarlagiin-burtgel/
├── venv/
└── main.pyzarlaga.json файлыг бид ГАРААР үүсгэхгүй — программ өөрөө үүсгэнэ.
Код бичихээсээ өмнө Бүлэг 5.4-ийн асуултыг тавья: өгөгдлөө яаж бүтцэлэх вэ?
Зарлага НЭГ БҮР нь юу вэ? Нэр, дүн, ангилалтай — гурван НЭРЛЭСЭН талбар. → dictionary.
Зарлагууд ОЛОН байна уу? Тийм, шинэ нь нэмэгдэнэ, бүгдийг эргүүлж явна. → list.
Зарлагыг нэрээр нь хайх нь гол үйлдэл үү? Үгүй — ижил нэртэй хоёр зарлага байж болно ("Ном" -ыг хоёр удаа худалдаж авч болно). → dictionary-key ашиглахгүй.
Дүгнэлт:
python
list[dict[str, str | int]]Яг Бүлэг 5.4-т урьдчилан ярьсан бүтэц. Мөн энэ бол JSON-ы байгалийн хэлбэр — файлд хадгалахад ямар ч хувиргалт шаардлагагүй.
Бүх программын суурь болох хоёр функцээс эхэлье.
python
# main.py
# Зарлагын бүртгэл — Түвшин 2 guided build
import json
ZAM = "zarlaga.json"
ANGILAL: list[str] = ["хоол", "тээвэр", "боловсрол", "хувцас", "бусад"]
Zarlaga = dict[str, str | int]
def achaalakh(zam: str) -> list[Zarlaga]:
try:
with open(zam, "r", encoding="utf-8") as f:
return json.load(f)
except FileNotFoundError:
return []
except json.JSONDecodeError:
print("Анхаар: файл эвдэрсэн байна. Хоосон бүртгэлээс эхэлье.")
return []
def khadgalakh(zam: str, zarlaguud: list[Zarlaga]) -> None:
with open(zam, "w", encoding="utf-8") as f:
json.dump(zarlaguud, f, ensure_ascii=False, indent=2)Хэдэн зүйлийг тайлбарлая.
ZAM ба ANGILAL нь ТОМ ҮСГЭЭР бичигдсэн. Энэ бол Python-ы заншил: том үсэгтэй нэр нь тогтмол (constant) гэсэн утгатай — "энэ утга программын турш өөрчлөгдөхгүй." Python нь албадан хамгаалахгүй, гэхдээ уншигчид тодорхой болгоно.
Zarlaga = dict[str, str | int] — энэ бол шинэ зүйл, type alias (төрлийн өөр нэр) гэж нэрлэгддэг.
Бид dict[str, str | int] гэсэн урт бичлэгийг олон удаа давтах ёстой байсан. Тэр нь ядаргаатай бөгөөд уншихад хүнд. Type alias нь түүнд БОГИНО НЭР өгч байна.
Одоо list[Zarlaga] гэж бичихэд list[dict[str, str | int]] гэсэнтэй ЯГ ИЖИЛ утгатай — гэхдээ хамаагүй тод. Мөн хэрэв бүтэц өөрчлөгдвөл, зөвхөн НЭГ мөр засахад л хангалттай.
Энэ бол Бүлэг 4-т ярьсан "hint нь муухай болж эхэлбэл, илүү сайн бүтэц хэрэгтэй" гэсэн санааны эхний алхам. Бүлэг 9-д бид түүнийг dataclass -аар бүрэн шийднэ, тэр үед Zarlaga нь жинхэнэ class болно. Одоохондоо alias нь сайн завсрын шийдэл.
achaalakh нь Бүлэг 6.2-ын хамгаалалттай унших хэв маягийг ашиглаж байна: файл байхгүй бол хоосон жагсаалт, эвдэрсэн бол анхааруулаад хоосон жагсаалт.
khadgalakh нь ensure_ascii=False, indent=2 — монгол текст, хүн уншихад тааламжтай.
Одоо ажиллуулж шалгая:
python
zarlaguud = achaalakh(ZAM)
print(f"Ачаалсан: {len(zarlaguud)} зарлага")
khadgalakh(ZAM, zarlaguud)
print("Хадгаллаа.")powershell
python main.pyАчаалсан: 0 зарлага
Хадгаллаа.zarlaga.json файл үүссэн байх ёстой. Нээж хараарай — дотор нь [] байна. Хоосон жагсаалт.
Одоо зарлага нэмэх функц. Гэхдээ эхлээд цэвэрлэх — Бүлэг 6.4-ийн сургамж.
python
def zarlaga_uusgekh(ner: str, dun_str: str, angilal: str) -> Zarlaga | None:
ner_tsever = ner.strip()
if not ner_tsever:
print("Алдаа: нэр хоосон байж болохгүй.")
return None
try:
dun = int(dun_str.strip())
except ValueError:
print(f"Алдаа: '{dun_str}' нь тоо биш байна.")
return None
if dun <= 0:
print("Алдаа: дүн эерэг тоо байх ёстой.")
return None
angilal_tsever = angilal.strip().lower()
if not angilal_tsever:
angilal_tsever = "бусад"
return {"ner": ner_tsever, "dun": dun, "angilal": angilal_tsever}Функцийг задалъя.
Оролт нь БҮГД string — учир нь энэ функц хэрэглэгчийн input() -ээс шууд ирэх утгуудтай ажиллана, тэдгээр нь үргэлж string байдаг (Курс 1-ээс санаж байгаа байх).
Буцаах нь Zarlaga | None — Бүлэг 4.3-ын union. "Хүчинтэй зарлага ЭСВЭЛ юу ч биш." Гарчиг нь дуудагчид "шалгаарай" гэж хэлж байна.
Гурван шалгалт: нэр хоосон биш, дүн нь тоо, дүн нь эерэг. Аль нэг нь бүтэлгүйтвэл — тодорхой мессеж хэвлээд None буцаана.
Нормчлол: .strip() ба .lower(). Хэрэглэгч " ХООЛ " гэж бичсэн ч "хоол" болно.
Алдааны мессежийг ФУНКЦ ДОТОР хэвлэж байгаа нь хамгийн цэвэрхэн дизайн биш (функц нь хэвлэхээс илүү зөвхөн шалгах ёстой байсан), гэхдээ энэ хэмжээний программд бүрэн зохимжтой. Бүлэг 13-т raise -ийг үзэхэд бид илүү сайн аргыг сурна.
Одоо нэмэх функц:
python
def zarlaga_nemekh(zarlaguud: list[Zarlaga], zarlaga: Zarlaga) -> None:
zarlaguud.append(zarlaga)
khadgalakh(ZAM, zarlaguud)
print(f"Нэмлээ: {zarlaga['ner']} — {zarlaga['dun']:,} төгрөг")Хамгийн чухал мөр нь khadgalakh(ZAM, zarlaguud). Бүлэг 6.2-ын сургамж: өгөгдлийг өөрчлөх бүрдээ ХАДГАЛ. Хэрэв түүнийг мартвал программ хаагдахад бүх зарлага алга болно.
Туршиж үзье:
python
zarlaguud = achaalakh(ZAM)
shine = zarlaga_uusgekh("Ном", "15000", "боловсрол")
if shine is not None:
zarlaga_nemekh(zarlaguud, shine)
shine = zarlaga_uusgekh(" Автобус ", "1000", " ТЭЭВЭР ")
if shine is not None:
zarlaga_nemekh(zarlaguud, shine)
shine = zarlaga_uusgekh("Тест", "тэнэг", "хоол")
if shine is not None:
zarlaga_nemekh(zarlaguud, shine)
print(zarlaguud)Нэмлээ: Ном — 15,000 төгрөг
Нэмлээ: Автобус — 1,000 төгрөг
Алдаа: 'тэнэг' нь тоо биш байна.
[{'ner': 'Ном', 'dun': 15000, 'angilal': 'боловсрол'}, {'ner': 'Автобус', 'dun': 1000, 'angilal': 'тээвэр'}]Цэвэрлэлт ажиллаж байна: зай арилсан, "ТЭЭВЭР" нь "тээвэр" болсон, буруу тоо нь хаягдсан, программ унасангүй.
zarlaga.json файлыг нээж хараарай — хоёр зарлага бичигдсэн байна.
python
def zarlaga_jagsaakh(zarlaguud: list[Zarlaga]) -> None:
if not zarlaguud:
print("\nЗарлага байхгүй байна.")
return
print()
print("=" * 52)
print(f"{'#':<4} {'НЭР':<16} {'ДҮН':>12} {'АНГИЛАЛ':<12}")
print("=" * 52)
for dugaar, z in enumerate(zarlaguud, start=1):
print(f"{dugaar:<4} {z['ner']:<16} {z['dun']:>12,} {z['angilal']:<12}")
print("=" * 52)
niit = sum(z["dun"] for z in zarlaguud)
print(f"{'НИЙТ':<21} {niit:>12,}")
print("=" * 52)Ажиллуулбал:
====================================================
# НЭР ДҮН АНГИЛАЛ
====================================================
1 Ном 15,000 боловсрол
2 Автобус 1,000 тээвэр
====================================================
НИЙТ 16,000
====================================================Бүлэг 6.4-ийн формат заагчид ажиллаж байна: {:<16} (зүүн зэрэгцүүлэлт), {:>12,} (баруун зэрэгцүүлэлт + мянгатын таслал).
enumerate(zarlaguud, start=1) (Бүлэг 2.3) нь дугаар өгч байна. Тэр дугаар нь дараа устгах үед хэрэгтэй болно.
Чухал нарийвчлал: дугаар нь 1 -ээс эхэлж байна, гэхдээ жагсаалтын index нь 0 -ээс эхэлдэг. Устгах үед тэр зөрүүг санах хэрэгтэй.
Бүлэг 6.4-ийн бүлэглэх хэв маягийг ашиглая.
python
def angilalaar_buleglekh(zarlaguud: list[Zarlaga]) -> dict[str, int]:
bulgeer: dict[str, int] = {}
for z in zarlaguud:
angilal = str(z["angilal"])
bulgeer[angilal] = bulgeer.get(angilal, 0) + int(z["dun"])
return bulgeer
def tailan_khevlekh(zarlaguud: list[Zarlaga]) -> None:
if not zarlaguud:
print("\nЗарлага байхгүй байна.")
return
niit = sum(int(z["dun"]) for z in zarlaguud)
bulgeer = angilalaar_buleglekh(zarlaguud)
erembelsen = sorted(zarlaguud, key=lambda z: int(z["dun"]), reverse=True)
print()
print("=" * 52)
print("ЗАРЛАГЫН ТАЙЛАН")
print("=" * 52)
print(f"Бичлэгийн тоо: {len(zarlaguud)}")
print(f"Нийт дүн: {niit:,} төгрөг")
print(f"Дундаж: {niit // len(zarlaguud):,} төгрөг")
print()
print("АНГИЛАЛААР")
print("-" * 52)
for angilal, dun in sorted(bulgeer.items(), key=lambda h: h[1], reverse=True):
khuvi = dun / niit * 100
print(f" {angilal:<14} {dun:>12,} ({khuvi:>5.1f}%)")
print()
print("ХАМГИЙН ҮНЭТЭЙ")
print("-" * 52)
for baiirlal, z in enumerate(erembelsen[:3], start=1):
print(f" {baiirlal}. {z['ner']:<14} {z['dun']:>12,}")
print("=" * 52)Энд int(z["dun"]) ба str(z["angilal"]) гэсэн хувиргалтууд гарч ирлээ. Яагаад?
Учир нь Zarlaga -гийн hint нь dict[str, str | int] — value нь string ЭСВЭЛ int байж болно. Editor нь z["dun"] -ыг str | int гэж үзнэ, тиймээс sum() эсвэл > ашиглахад анхааруулна.
Бид өөрсдөө мэдэж байна, dun нь ҮРГЭЛЖ int (цэвэрлэх функц баталгаажуулсан). Гэхдээ hint нь тэр мэдлэгийг илэрхийлж чадахгүй.
Энэ бол dict[str, str | int] -ийн жинхэнэ өртөг. Бүлэг 4-т би "hint нь муухай болж эхэлбэл, тэр нь илүү сайн бүтэц хэрэгтэй байгаагийн дохио" гэж хэлсэн. Одоо та тэр өртгийг өөрийн нүдээр харж байна.
Бүлэг 9-д dataclass ашиглавал:
python
@dataclass
class Zarlaga:
ner: str
dun: int
angilal: strТэгвэл z.dun нь editor-ын хувьд ҮРГЭЛЖ int болно. int(...) хувиргалт огт хэрэггүй. Код нь цэвэрхэн болно.
Одоохондоо int() -ийг бичээд өнгөрөөрэй — тэдгээр нь ажилладаг, зөвхөн муухай. Гурван бүлгийн дараа бид тэднийг арилгана.
python
def angilalaar_shuukh(zarlaguud: list[Zarlaga], angilal: str) -> list[Zarlaga]:
angilal_tsever = angilal.strip().lower()
return [z for z in zarlaguud if z["angilal"] == angilal_tsever]
def zarlaga_ustgakh(zarlaguud: list[Zarlaga], dugaar: int) -> bool:
if dugaar < 1 or dugaar > len(zarlaguud):
print(f"Алдаа: {dugaar} гэсэн дугаартай зарлага байхгүй.")
return False
ustgasan = zarlaguud.pop(dugaar - 1)
khadgalakh(ZAM, zarlaguud)
print(f"Устгалаа: {ustgasan['ner']} — {ustgasan['dun']:,} төгрөг")
return Trueangilalaar_shuukh нь оролтыг ч цэвэрлэж байгааг анзаараарай — .strip().lower(). Хэрэглэгч "ХООЛ" гэж бичсэн ч ажиллана. Бүлэг 6.4-ийн "хилийн цэг бүрд цэвэрлэ" гэсэн зарчим.
zarlaga_ustgakh -д хоёр чухал зүйл байна.
Нэгдүгээрт, хязгаарын шалгалт. if dugaar < 1 or dugaar > len(zarlaguud) — хэрэглэгч 0, -5, эсвэл 999 гэж оруулж болно. Шалгалтгүй бол IndexError гарч программ унана.
Хоёрдугаарт, dugaar - 1. Хэрэглэгч 1 гэж бичсэн бол тэр нь ЭХНИЙ зарлага, гэвч list-ийн index нь 0. Нэгийг хасах ёстой. Энэ бол маш түгээмэл алдааны эх үүсвэр (программистууд түүнийг "off-by-one error" гэж нэрлэдэг).
.pop(index) нь элементийг ХАСААД, түүнийг БУЦААНА. Тиймээс бид устгасан зарлагыг хэвлэж чадаж байна.
-> bool нь "амжилттай эсэх" -ийг буцаана. Бүлэг 4.4-ийн жишээтэй ижил хэв маяг.
Одоо Курс 1-ийн хийх ажлын жагсаалтын хэв маягаар цэс барья.
python
def tsees_kheveleкh() -> None:
print()
print("=" * 52)
print("ЗАРЛАГЫН БҮРТГЭЛ")
print("=" * 52)
print("1. Зарлага нэмэх")
print("2. Бүх зарлага харах")
print("3. Тайлан")
print("4. Ангилалаар шүүх")
print("5. Зарлага устгах")
print("0. Гарах")
print("=" * 52)
def zarlaga_nemekh_asuukh(zarlaguud: list[Zarlaga]) -> None:
print()
ner = input("Нэр: ")
dun = input("Дүн (төгрөг): ")
print(f"Ангилал ({', '.join(ANGILAL)}):")
angilal = input("Ангилал: ")
shine = zarlaga_uusgekh(ner, dun, angilal)
if shine is None:
print("Зарлага нэмэгдсэнгүй.")
return
zarlaga_nemekh(zarlaguud, shine)
def shuukh_asuukh(zarlaguud: list[Zarlaga]) -> None:
print()
angilal = input("Ямар ангилал: ")
oldson = angilalaar_shuukh(zarlaguud, angilal)
if not oldson:
print(f"'{angilal.strip().lower()}' ангилалд зарлага олдсонгүй.")
return
zarlaga_jagsaakh(oldson)
def ustgakh_asuukh(zarlaguud: list[Zarlaga]) -> None:
if not zarlaguud:
print("\nУстгах зарлага байхгүй.")
return
zarlaga_jagsaakh(zarlaguud)
print()
oroult = input("Устгах зарлагын дугаар: ")
try:
dugaar = int(oroult.strip())
except ValueError:
print("Алдаа: дугаар нь тоо байх ёстой.")
return
zarlaga_ustgakh(zarlaguud, dugaar)
def gol_loop() -> None:
zarlaguud = achaalakh(ZAM)
print(f"Ачаалсан: {len(zarlaguud)} зарлага")
while True:
tsees_kheveleкh()
songolt = input("Сонголт: ").strip()
if songolt == "1":
zarlaga_nemekh_asuukh(zarlaguud)
elif songolt == "2":
zarlaga_jagsaakh(zarlaguud)
elif songolt == "3":
tailan_khevlekh(zarlaguud)
elif songolt == "4":
shuukh_asuukh(zarlaguud)
elif songolt == "5":
ustgakh_asuukh(zarlaguud)
elif songolt == "0":
print("\nБаяртай.")
break
else:
print("\nБуруу сонголт. 0-5 хооронд сонгоно уу.")
gol_loop()Хэдэн зүйлийг тайлбарлая.
', '.join(ANGILAL) — жагсаалтыг таслалаар холбож string болгодог. Курс 1-ээс танил. Үр дүн: "хоол, тээвэр, боловсрол, хувцас, бусад".
Функцүүдийн ХОЁР ДАВХАРГА. Анзаараарай, zarlaga_nemekh (цэвэр логик) ба zarlaga_nemekh_asuukh (хэрэглэгчээс асуух) гэсэн ХОЁР функц байна.
Яагаад тусад нь? Учир нь:
zarlaga_nemekh нь input() мэдэхгүй. Түүнийг ямар ч эх сурвалжаас (файл, тест, интернэт) дуудаж болно.
zarlaga_nemekh_asuukh нь зөвхөн ХЭРЭГЛЭГЧТЭЙ ярина.
Энэ бол ЛОГИК ба ИНТЕРФЕЙС -ийг салгах гэсэн чухал зарчим. Бүлэг 12-т олон файлтай төсөл бичихэд яг энэ салгалт нь файлын хуваарь болно: logic.py ба display.py.
while True: ба break — Курс 1-ээс танил хязгааргүй loop. Хэрэглэгч 0 сонгох хүртэл үргэлжилнэ.
Одоо бүх хэсгийг нэг файлд нийлүүлье.
python
# main.py
# Зарлагын бүртгэл — Түвшин 2 guided build
# Бүрэн type hint-тэй, JSON файлд хадгалагдана
import json
ZAM = "zarlaga.json"
ANGILAL: list[str] = ["хоол", "тээвэр", "боловсрол", "хувцас", "бусад"]
Zarlaga = dict[str, str | int]
# ─── ФАЙЛ ────────────────────────────────────────────────────────
def achaalakh(zam: str) -> list[Zarlaga]:
try:
with open(zam, "r", encoding="utf-8") as f:
return json.load(f)
except FileNotFoundError:
return []
except json.JSONDecodeError:
print("Анхаар: файл эвдэрсэн байна. Хоосон бүртгэлээс эхэлье.")
return []
def khadgalakh(zam: str, zarlaguud: list[Zarlaga]) -> None:
with open(zam, "w", encoding="utf-8") as f:
json.dump(zarlaguud, f, ensure_ascii=False, indent=2)
# ─── ЛОГИК ───────────────────────────────────────────────────────
def zarlaga_uusgekh(ner: str, dun_str: str, angilal: str) -> Zarlaga | None:
ner_tsever = ner.strip()
if not ner_tsever:
print("Алдаа: нэр хоосон байж болохгүй.")
return None
try:
dun = int(dun_str.strip())
except ValueError:
print(f"Алдаа: '{dun_str}' нь тоо биш байна.")
return None
if dun <= 0:
print("Алдаа: дүн эерэг тоо байх ёстой.")
return None
angilal_tsever = angilal.strip().lower()
if not angilal_tsever:
angilal_tsever = "бусад"
return {"ner": ner_tsever, "dun": dun, "angilal": angilal_tsever}
def zarlaga_nemekh(zarlaguud: list[Zarlaga], zarlaga: Zarlaga) -> None:
zarlaguud.append(zarlaga)
khadgalakh(ZAM, zarlaguud)
print(f"Нэмлээ: {zarlaga['ner']} — {zarlaga['dun']:,} төгрөг")
def zarlaga_ustgakh(zarlaguud: list[Zarlaga], dugaar: int) -> bool:
if dugaar < 1 or dugaar > len(zarlaguud):
print(f"Алдаа: {dugaar} гэсэн дугаартай зарлага байхгүй.")
return False
ustgasan = zarlaguud.pop(dugaar - 1)
khadgalakh(ZAM, zarlaguud)
print(f"Устгалаа: {ustgasan['ner']} — {ustgasan['dun']:,} төгрөг")
return True
def angilalaar_shuukh(zarlaguud: list[Zarlaga], angilal: str) -> list[Zarlaga]:
angilal_tsever = angilal.strip().lower()
return [z for z in zarlaguud if z["angilal"] == angilal_tsever]
def angilalaar_buleglekh(zarlaguud: list[Zarlaga]) -> dict[str, int]:
bulgeer: dict[str, int] = {}
for z in zarlaguud:
angilal = str(z["angilal"])
bulgeer[angilal] = bulgeer.get(angilal, 0) + int(z["dun"])
return bulgeer
# ─── ХАРУУЛАХ ────────────────────────────────────────────────────
def zarlaga_jagsaakh(zarlaguud: list[Zarlaga]) -> None:
if not zarlaguud:
print("\nЗарлага байхгүй байна.")
return
print()
print("=" * 52)
print(f"{'#':<4} {'НЭР':<16} {'ДҮН':>12} {'АНГИЛАЛ':<12}")
print("=" * 52)
for dugaar, z in enumerate(zarlaguud, start=1):
print(f"{dugaar:<4} {z['ner']:<16} {z['dun']:>12,} {z['angilal']:<12}")
print("=" * 52)
niit = sum(int(z["dun"]) for z in zarlaguud)
print(f"{'НИЙТ':<21} {niit:>12,}")
print("=" * 52)
def tailan_khevlekh(zarlaguud: list[Zarlaga]) -> None:
if not zarlaguud:
print("\nЗарлага байхгүй байна.")
return
niit = sum(int(z["dun"]) for z in zarlaguud)
bulgeer = angilalaar_buleglekh(zarlaguud)
erembelsen = sorted(zarlaguud, key=lambda z: int(z["dun"]), reverse=True)
print()
print("=" * 52)
print("ЗАРЛАГЫН ТАЙЛАН")
print("=" * 52)
print(f"Бичлэгийн тоо: {len(zarlaguud)}")
print(f"Нийт дүн: {niit:,} төгрөг")
print(f"Дундаж: {niit // len(zarlaguud):,} төгрөг")
print()
print("АНГИЛАЛААР")
print("-" * 52)
for angilal, dun in sorted(bulgeer.items(), key=lambda h: h[1], reverse=True):
khuvi = dun / niit * 100
print(f" {angilal:<14} {dun:>12,} ({khuvi:>5.1f}%)")
print()
print("ХАМГИЙН ҮНЭТЭЙ")
print("-" * 52)
for baiirlal, z in enumerate(erembelsen[:3], start=1):
print(f" {baiirlal}. {z['ner']:<14} {z['dun']:>12,}")
print("=" * 52)
# ─── ХЭРЭГЛЭГЧИЙН ИНТЕРФЕЙС ──────────────────────────────────────
def tsees_kheveleкh() -> None:
print()
print("=" * 52)
print("ЗАРЛАГЫН БҮРТГЭЛ")
print("=" * 52)
print("1. Зарлага нэмэх")
print("2. Бүх зарлага харах")
print("3. Тайлан")
print("4. Ангилалаар шүүх")
print("5. Зарлага устгах")
print("0. Гарах")
print("=" * 52)
def zarlaga_nemekh_asuukh(zarlaguud: list[Zarlaga]) -> None:
print()
ner = input("Нэр: ")
dun = input("Дүн (төгрөг): ")
print(f"Ангилал ({', '.join(ANGILAL)}):")
angilal = input("Ангилал: ")
shine = zarlaga_uusgekh(ner, dun, angilal)
if shine is None:
print("Зарлага нэмэгдсэнгүй.")
return
zarlaga_nemekh(zarlaguud, shine)
def shuukh_asuukh(zarlaguud: list[Zarlaga]) -> None:
print()
angilal = input("Ямар ангилал: ")
oldson = angilalaar_shuukh(zarlaguud, angilal)
if not oldson:
print(f"'{angilal.strip().lower()}' ангилалд зарлага олдсонгүй.")
return
zarlaga_jagsaakh(oldson)
def ustgakh_asuukh(zarlaguud: list[Zarlaga]) -> None:
if not zarlaguud:
print("\nУстгах зарлага байхгүй.")
return
zarlaga_jagsaakh(zarlaguud)
print()
oroult = input("Устгах зарлагын дугаар: ")
try:
dugaar = int(oroult.strip())
except ValueError:
print("Алдаа: дугаар нь тоо байх ёстой.")
return
zarlaga_ustgakh(zarlaguud, dugaar)
def gol_loop() -> None:
zarlaguud = achaalakh(ZAM)
print(f"Ачаалсан: {len(zarlaguud)} зарлага")
while True:
tsees_kheveleкh()
songolt = input("Сонголт: ").strip()
if songolt == "1":
zarlaga_nemekh_asuukh(zarlaguud)
elif songolt == "2":
zarlaga_jagsaakh(zarlaguud)
elif songolt == "3":
tailan_khevlekh(zarlaguud)
elif songolt == "4":
shuukh_asuukh(zarlaguud)
elif songolt == "5":
ustgakh_asuukh(zarlaguud)
elif songolt == "0":
print("\nБаяртай.")
break
else:
print("\nБуруу сонголт. 0-5 хооронд сонгоно уу.")
gol_loop()powershell
python main.pyАчаалсан: 2 зарлага
====================================================
ЗАРЛАГЫН БҮРТГЭЛ
====================================================
1. Зарлага нэмэх
2. Бүх зарлага харах
3. Тайлан
4. Ангилалаар шүүх
5. Зарлага устгах
0. Гарах
====================================================
Сонголт: 1
Нэр: Хоол
Дүн (төгрөг): 8500
Ангилал (хоол, тээвэр, боловсрол, хувцас, бусад):
Ангилал: хоол
Нэмлээ: Хоол — 8,500 төгрөгХэдэн зарлага нэмээд, 3 сонгоод тайлан харъя:
Сонголт: 3
====================================================
ЗАРЛАГЫН ТАЙЛАН
====================================================
Бичлэгийн тоо: 4
Нийт дүн: 46,500 төгрөг
Дундаж: 11,625 төгрөг
АНГИЛАЛААР
----------------------------------------------------
боловсрол 37,000 ( 79.6%)
хоол 8,500 ( 18.3%)
тээвэр 1,000 ( 2.2%)
ХАМГИЙН ҮНЭТЭЙ
----------------------------------------------------
1. Ном 22,000
2. Ном 15,000
3. Хоол 8,500
====================================================Одоо программаас гараад (0), ДАХИН ажиллуулаарай.
Ачаалсан: 4 зарлагаӨгөгдөл үлдлээ. Энэ бол Курс 1-ийн хийх ажлын жагсаалтаас хамгийн том ялгаа: тэр программ хаагдахад бүх зүйлээ мартдаг байсан. Энэ нь мартахгүй.
Дараах гурван зүйл нь бүрэн сонголттой. Хийхгүй бол ямар ч асуудалгүй — дараагийн бүлгүүд эдгээрийг таамаглахгүй.
Өргөтгөл 1: CSV экспорт. Бүлэг 6.3-ын мэдлэгийг ашиглан тайланг Excel-д илгээж болно.
python
import csv
def csv_ruu_gargakh(zarlaguud: list[Zarlaga], zam: str = "zarlaga.csv") -> None:
if not zarlaguud:
print("Экспортлох зарлага байхгүй.")
return
with open(zam, "w", encoding="utf-8-sig", newline="") as f:
bichigch = csv.DictWriter(f, fieldnames=["ner", "dun", "angilal"])
bichigch.writeheader()
bichigch.writerows(zarlaguud)
print(f"{len(zarlaguud)} зарлагыг {zam} файлд экспортлолоо.")encoding="utf-8-sig" — Excel-д кирилл үсэг зөв харагдана. Цэсэнд 6. CSV экспорт гэж нэмээд, gol_loop -д elif songolt == "6": мөр нэмнэ.
Өргөтгөл 2: огноо нэмэх. Зарлага бүрд огноо хадгалж болно.
python
from datetime import date
def zarlaga_uusgekh(...) -> Zarlaga | None:
...
return {
"ner": ner_tsever,
"dun": dun,
"angilal": angilal_tsever,
"ognoo": date.today().isoformat(),
}date.today().isoformat() нь "2026-07-13" гэсэн string өгнө. Бүлэг 6.1-ээс санаж байгаа байх: JSON-д огноо байхгүй тул string болгож хадгална.
Өргөтгөл 3: дүнгээр шүүх. "5000-аас дээш бүх зарлага" гэх мэт.
python
def dungeer_shuukh(zarlaguud: list[Zarlaga], dood: int) -> list[Zarlaga]:
return [z for z in zarlaguud if int(z["dun"]) >= dood]1. Хадгалахаа мартах
python
def zarlaga_nemekh(zarlaguud: list[Zarlaga], zarlaga: Zarlaga) -> None:
zarlaguud.append(zarlaga)
# khadgalakh(ZAM, zarlaguud) ← МАРТСАНПрограмм ажиллаж байгаа мэт харагдана. Гэвч дахин ажиллуулахад бүх зарлага алга. Өөрчлөх бүрдээ хадгал.
2. Off-by-one — устгах үед
python
ustgasan = zarlaguud.pop(dugaar) # БУРУУ
ustgasan = zarlaguud.pop(dugaar - 1) # ЗӨВХэрэглэгч 1 гэж бичсэн бол index нь 0. Хэрэв хасахаа мартвал — буруу зарлага устана, эсвэл IndexError.
3. Хязгаарын шалгалт мартах
python
zarlaguud.pop(dugaar - 1)Хэрэглэгч 999 гэж бичвэл:
IndexError: pop index out of rangeif dugaar < 1 or dugaar > len(zarlaguud) -ээр урьдчилан шалга.
4. Хоосон жагсаалт дээр хуваах
python
niit // len(zarlaguud)ZeroDivisionError: integer division or modulo by zeroif not zarlaguud: return -ээр хамгаал.
5. Оролтыг цэвэрлэхгүй байх
Хэрэглэгч " ХООЛ " гэж бичвэл, .strip().lower() -гүй бол шинэ ангилал үүснэ. Тайлан буруу гарна.
6. int(z["dun"]) -ыг мартах
python
niit = sum(z["dun"] for z in zarlaguud)Ажиллана (dun нь үнэндээ int тул), гэхдээ editor нь анхааруулна — учир нь hint нь str | int. Бүлэг 9-д dataclass ашиглавал энэ асуудал бүрмөсөн арилна.
Түвшин 2-ын бүх мэдлэг нэг программд нийллээ: type hint (Бүлэг 4), dictionary/list бүтэц (Бүлэг 5), JSON хадгалалт (Бүлэг 6.2), цэвэрлэлт (Бүлэг 6.4).
Бүтцийн шийдвэр: list[dict[str, str | int]] — dictionary-уудын жагсаалт. JSON-ы байгалийн хэлбэр.
Type alias: Zarlaga = dict[str, str | int] — урт hint-д богино нэр өгнө. Кодыг тод болгоно.
Тогтмолууд том үсгээр: ZAM, ANGILAL.
Гурван давхарга: ФАЙЛ (ачаалах/хадгалах) → ЛОГИК (цэвэр функцууд, input() мэдэхгүй) → ИНТЕРФЕЙС (хэрэглэгчээс асуух). Энэ салгалт нь Бүлэг 12-ын олон файлтай төслийн бэлтгэл.
Өгөгдлийг өөрчлөх бүрдээ ХАДГАЛ — эс тэгвээс программ хаагдахад бүх зүйл алга.
Хилийн цэг бүрд цэвэрлэ: файлаас ирсэн ч, хэрэглэгчээс ирсэн ч — .strip(), .lower(), int() + try/except.
Off-by-one: хэрэглэгчийн 1 нь index 0. Устгахдаа dugaar - 1.
Хязгаарын шалгалт: IndexError, ZeroDivisionError -ээс урьдчилан хамгаал.
dict[str, str | int] -ийн өртөг: int(z["dun"]) гэсэн хувиргалт хаа сайгүй хэрэгтэй болж байна. Бүлэг 9-ийн dataclass түүнийг бүрмөсөн арилгана.
ТҮВШИН 2 ДУУСЛАА. Та одоо орчин үеийн Python-ы өгөгдлийн ажлыг бүрэн эзэмшлээ: type hint, бүх бүтэц, JSON, CSV, цэвэрлэлт. Мөн та жинхэнэ, файлд хадгалагддаг программ бичлээ.
Түвшин 3-д бид OOP руу гүн орно. Курс 1-д та class гэж юу болохыг үзсэн — одоо түүнийг жинхэнэ эзэмшинэ. Inheritance (Бүлэг 7): class-ууд бие биенээсээ өвлөх нь. Special methods (Бүлэг 8): print(objekt) яагаад муухай харагддаг, түүнийг яаж засах вэ. Dataclass (Бүлэг 9): энэ хичээлийн бүх int(z["dun"]) -ыг арилгаж, z.dun гэж бичдэг болох арга. Мөн тэнд та анх удаа @ тэмдгийг харна — тэр нь Бүлэг 10-ын decorator руу хөтлөх шууд гүүр юм.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.