Ma'lumot tuzilmalari: lug'atdan dataclass gacha
Lug'at nima uchun yetarli emas
Hozirgacha bir qatorni lug'at bilan ifodaladik: {"ism": "Aziz", "guruh": "A", "baho": 85}. Bu oson,
lekin uch muammosi bor.
qator = {"ism": "Aziz", "guruh": "A", "baho": 85} print("1. Xato kalit — xato faqat ishga tushganda chiqadi:") try: print(qator["bahosi"]) except KeyError as e: print(" KeyError:", e) print("2. Hech kim tuzilishni kafolatlamaydi:") buzuq = {"ism": "Bobur"} print(" guruh bormi?", "guruh" in buzuq) print("3. Har joyda kalit nomini qo'lda yozasiz:") print(" qator['guruh'] — imlo xatosi qilsangiz, dastur jim ishlaydi va noto'g'ri natija beradi")
1. Xato kalit — xato faqat ishga tushganda chiqadi: KeyError: 'bahosi' 2. Hech kim tuzilishni kafolatlamaydi: guruh bormi? False 3. Har joyda kalit nomini qo'lda yozasiz: qator['guruh'] — imlo xatosi qilsangiz, dastur jim ishlaydi va noto'g'ri natija beradi
Uchinchisi eng yomoni: qator.get("gruh") (imlo xatosi bilan) None qaytaradi va dastur davom etadi.
dataclass: tuzilmani e'lon qilish
shu faylni yaratingsrc/hisobot/modellar.py
"""Ma'lumot tuzilmalari.""" from dataclasses import dataclass, field from enum import Enum class Daraja(str, Enum): """Baho darajasi. `str` dan meros oladi — JSON ga to'g'ridan-to'g'ri yoziladi.""" ALO = "a'lo" YAXSHI = "yaxshi" QONIQARLI = "qoniqarli" QONIQARSIZ = "qoniqarsiz" @classmethod def bahodan(cls, baho): if baho >= 90: return cls.ALO if baho >= 75: return cls.YAXSHI if baho >= 60: return cls.QONIQARLI return cls.QONIQARSIZ @dataclass(frozen=True, slots=True) class Talaba: """Bitta tekshirilgan qator. O'zgarmas (frozen) — tasodifan buzib bo'lmaydi.""" ism: str guruh: str baho: int sana: str @property def daraja(self): return Daraja.bahodan(self.baho) @dataclass class Natija: """Tekshiruv natijasi: to'g'ri qatorlar va xatolar.""" talabalar: list = field(default_factory=list) xatolar: list = field(default_factory=list) @property def jami(self): return len(self.talabalar) + len(self.xatolar)
.venv/bin/python3 -c " from hisobot.modellar import Talaba, Daraja, Natija t = Talaba(ism='Aziz', guruh='A', baho=85, sana='2026-09-01') print(t) print('daraja:', t.daraja, '| qiymati:', t.daraja.value) print('teng solishtirish ishlaydi:', t == Talaba('Aziz', 'A', 85, '2026-09-01')) "
Talaba(ism='Aziz', guruh='A', baho=85, sana='2026-09-01') daraja: Daraja.YAXSHI | qiymati: yaxshi teng solishtirish ishlaydi: True
Bitta e'londan ko'p narsa keldi: konstruktor, __repr__, __eq__. Qo'lda yozganda bu 20 qator
bo'lardi.
frozen=True: tasodifiy o'zgarishdan himoya
.venv/bin/python3 -c " from hisobot.modellar import Talaba t = Talaba('Aziz', 'A', 85, '2026-09-01') t.baho = 100 "
Traceback (most recent call last): File "<string>", line 4, in <module> File "<string>", line 4, in __setattr__ dataclasses.FrozenInstanceError: cannot assign to field 'baho'
Qaytish kodi 1. FrozenInstanceError — obyektni o'zgartirib bo'lmadi.
Nega bu yaxshi? Chunki ma'lumot bir necha funksiyadan o'tganda, ulardan biri uni jim o'zgartirib
qo'yishi mumkin. frozen=True buni imkonsiz qiladi.
O'zgartirish kerak bo'lsa — yangi nusxa yasaladi:
.venv/bin/python3 -c " from dataclasses import replace from hisobot.modellar import Talaba eski = Talaba('Aziz', 'A', 85, '2026-09-01') yangi = replace(eski, baho=92) print('eski :', eski.baho, eski.daraja.value) print('yangi:', yangi.baho, yangi.daraja.value) print('eski o\'zgarmadi:', eski.baho == 85) "
eski : 85 yaxshi yangi: 92 a'lo eski o'zgarmadi: True
slots=True: xotira va tezlik
slots=True obyektga qat'iy maydonlar ro'yxatini beradi. Ikki foydasi bor va ikkalasini ham
o'lchaymiz.
Birinchisi — imlo xatosini ushlaydi:
from dataclasses import dataclass @dataclass class SlotsSiz: ism: str baho: int @dataclass(slots=True) class SlotsBilan: ism: str baho: int a = SlotsSiz("Aziz", 85) a.bahosi = 90 # imlo xatosi: baho emas, bahosi print("slots YO'Q -> imlo xatosi jim o'tdi:", a.bahosi, "| haqiqiy baho:", a.baho) b = SlotsBilan("Aziz", 85) try: b.bahosi = 90 except AttributeError as e: print("slots BILAN -> AttributeError:", e)
slots YO'Q -> imlo xatosi jim o'tdi: 90 | haqiqiy baho: 85 slots BILAN -> AttributeError: 'SlotsBilan' object has no attribute 'bahosi'
Birinchi obyektda imlo xatosi jim o'tdi — a.bahosi yaratildi va dastur davom etdi. Bunday
xato soatlab qidiriladi. slots=True esa uni darhol ushladi.
:::gotcha Python 3.10 da frozen=True + slots=True
Ikkalasini birga ishlatganda Python 3.10 tushunarsiz xabar beradi. Tekshiramiz:
from dataclasses import dataclass import sys @dataclass(frozen=True, slots=True) class Ikkalasi: x: int print("Python", sys.version.split()[0]) try: Ikkalasi(1).xato_nom = 5 except Exception as e: print(f" {type(e).__name__}: {e}")
Python 3.10.12 TypeError: super(type, obj): obj must be an instance or subtype of type
Kutilgan AttributeError o'rniga TypeError va butunlay boshqa matn chiqdi. Bu Python 3.10 dagi
kamchilik va u 3.11 da tuzatilgan. Xulq to'g'ri (o'zgartirishga ruxsat berilmadi), lekin xabar
chalg'itadi — shuning uchun 3.10 da bunday xato ko'rsangiz, sababi shu ekanini biling.
:::
Ikkinchi foydasi — xotira. O'lchaymiz:
import sys, time from dataclasses import dataclass @dataclass class Oddiy: ism: str guruh: str baho: int sana: str @dataclass(slots=True) class Slotli: ism: str guruh: str baho: int sana: str def hajmi(obj): """Obyekt + uning __dict__ i (bo'lsa).""" h = sys.getsizeof(obj) if hasattr(obj, "__dict__"): h += sys.getsizeof(obj.__dict__) return h lugat = {"ism": "Aziz", "guruh": "A", "baho": 85, "sana": "2026-09-01"} o = Oddiy("Aziz", "A", 85, "2026-09-01") s = Slotli("Aziz", "A", 85, "2026-09-01") import pandas as pd show(pd.DataFrame([ {"tur": "dict", "bayt": hajmi(lugat)}, {"tur": "dataclass", "bayt": hajmi(o)}, {"tur": "dataclass(slots=True)", "bayt": hajmi(s)}, ])) def olch(f, n=200_000): f() t0 = time.perf_counter() for _ in range(n): f() return (time.perf_counter() - t0) / n * 1e6 show(pd.DataFrame([ {"amal": "yaratish", "dict, mks": round(olch(lambda: dict(ism="A", guruh="B", baho=1, sana="C")), 3), "dataclass, mks": round(olch(lambda: Oddiy("A", "B", 1, "C")), 3), "slots, mks": round(olch(lambda: Slotli("A", "B", 1, "C")), 3)}, {"amal": "maydon o'qish", "dict, mks": round(olch(lambda: lugat["baho"]), 3), "dataclass, mks": round(olch(lambda: o.baho), 3), "slots, mks": round(olch(lambda: s.baho), 3)}, ]))
| tur | bayt |
|---|---|
| dict | 232 |
| dataclass | 152 |
| dataclass(slots=True) | 64 |
| amal | dict, mks | dataclass, mks | slots, mks |
|---|---|---|---|
| yaratish | 0.069 | 0.12 | 0.099 |
| maydon o'qish | 0.025 | 0.026 | 0.023 |
Raqamlarga qarab qaror qiling, lekin asosiy sabab tezlik emas — imlo xatosini ushlash va tuzilishning hujjatlanishi.
Enum: cheklangan qiymatlar to'plami
Matn bilan ishlaganda imlo xatosi yana qaytadi:
def eski_usul(daraja): if daraja == "alo": # e'tibor bering: "a'lo" emas return "ajoyib" return "boshqa" print("matn bilan:", eski_usul("a'lo"), " <- jim noto'g'ri javob")
matn bilan: boshqa <- jim noto'g'ri javob
Enum bilan bunday xato darhol chiqadi:
.venv/bin/python3 -c " from hisobot.modellar import Daraja print('to\'g\'ri:', Daraja('a\'lo')) print('hamma qiymatlar:', [d.value for d in Daraja]) try: Daraja('alo') except ValueError as e: print('XATO (yaxshi):', e) "
to'g'ri: Daraja.ALO hamma qiymatlar: ["a'lo", 'yaxshi', 'qoniqarli', 'qoniqarsiz'] XATO (yaxshi): 'alo' is not a valid Daraja
str, Enum dan meros olgani uchun u matn kabi ham ishlaydi — JSON ga yozishda qulay:
.venv/bin/python3 -c " import json from hisobot.modellar import Daraja print(json.dumps({'daraja': Daraja.ALO})) print('matn sifatida solishtirish:', Daraja.ALO == 'a\'lo') "
{"daraja": "a'lo"}
matn sifatida solishtirish: True
Qaysi tuzilmani qachon
import pandas as pd show(pd.DataFrame([ {"tuzilma": "dict", "qachon": "Tuzilishi oldindan noma'lum; JSON dan endi o'qilgan xom ma'lumot"}, {"tuzilma": "dataclass", "qachon": "Ichki modellar — maydonlar ma'lum va barqaror"}, {"tuzilma": "dataclass(frozen)", "qachon": "Ma'lumot funksiyalar orasida yuradi; o'zgarmasligi kerak"}, {"tuzilma": "NamedTuple", "qachon": "Kichik, indeks bilan ham murojaat qilinadi; to'liq o'zgarmas"}, {"tuzilma": "Enum", "qachon": "Qiymatlar to'plami cheklangan: holat, daraja, tur"}, {"tuzilma": "pydantic.BaseModel", "qachon": "Tashqi ma'lumotni tekshirish kerak (11-bob)"}, ]))
| tuzilma | qachon |
|---|---|
| dict | Tuzilishi oldindan noma'lum; JSON dan endi o'qilgan xom ma'lumot |
| dataclass | Ichki modellar — maydonlar ma'lum va barqaror |
| dataclass(frozen) | Ma'lumot funksiyalar orasida yuradi; o'zgarmasligi kerak |
| NamedTuple | Kichik, indeks bilan ham murojaat qilinadi; to'liq o'zgarmas |
| Enum | Qiymatlar to'plami cheklangan: holat, daraja, tur |
| pydantic.BaseModel | Tashqi ma'lumotni tekshirish kerak (11-bob) |
Muhim chegara: tashqi ma'lumot (fayl, API, foydalanuvchi) avval dict bo'lib keladi, siz uni
tekshirib ichki modelga aylantirasiz. Undan keyingi kod faqat model bilan ishlaydi.
Loyihani modelga o'tkazamiz
shu faylni yaratingsrc/hisobot/oqish.py
"""CSV o'qish va qatorlarni tekshirib modelga aylantirish.""" import csv from .modellar import Natija, Talaba from .tekshiruv import bahoni_tekshir def oqi(yol): """Faqat o'qiydi: lug'atlar ro'yxati.""" with open(yol, encoding="utf-8") as f: return list(csv.DictReader(f)) def tozala(qatorlar): """Xom lug'atlarni Talaba obyektlariga aylantiradi.""" natija = Natija() for i, q in enumerate(qatorlar, start=2): try: natija.talabalar.append(Talaba( ism=q.get("ism", "").strip(), guruh=q.get("guruh", "").strip(), baho=bahoni_tekshir(q.get("baho")), sana=q.get("sana", "").strip(), )) except ValueError as e: natija.xatolar.append((i, q.get("ism", "?"), str(e))) return natija
shu faylni yaratingsrc/hisobot/hisob.py
"""Statistika: kiritish/chiqarishsiz, faqat hisob.""" from collections import Counter def guruh_ortachasi(talabalar): yigindi = {} for t in talabalar: jami, soni = yigindi.get(t.guruh, (0, 0)) yigindi[t.guruh] = (jami + t.baho, soni + 1) return {g: round(jami / soni, 2) for g, (jami, soni) in sorted(yigindi.items())} def daraja_taqsimoti(talabalar): return dict(Counter(t.daraja.value for t in talabalar).most_common())
shu faylni yaratingsrc/hisobot/__main__.py
"""python3 -m hisobot bilan ishga tushadi.""" import sys from .hisob import daraja_taqsimoti, guruh_ortachasi from .oqish import oqi, tozala def main(argv=None): argv = sys.argv[1:] if argv is None else argv if not argv: print("ishlatish: python3 -m hisobot <fayl.csv>") return 2 n = tozala(oqi(argv[0])) print(f"jami: {n.jami}, to'g'ri: {len(n.talabalar)}, xato: {len(n.xatolar)}") for g, o in guruh_ortachasi(n.talabalar).items(): print(f" {g}: {o}") print("darajalar:", daraja_taqsimoti(n.talabalar)) for qator, ism, sabab in n.xatolar: print(f" {qator}-qator ({ism}): {sabab}") return 0 if not n.xatolar else 1 if __name__ == "__main__": sys.exit(main())
shu faylni yaratingsrc/hisobot/__init__.py
"""CSV ma'lumotini tekshirib hisobot chiqaradigan asbob.""" from .hisob import daraja_taqsimoti, guruh_ortachasi from .modellar import Daraja, Natija, Talaba from .oqish import oqi, tozala from .tekshiruv import bahoni_tekshir __version__ = "0.4.0" __all__ = ["daraja_taqsimoti", "guruh_ortachasi", "Daraja", "Natija", "Talaba", "oqi", "tozala", "bahoni_tekshir", "__version__"]
.venv/bin/python3 -c " import hisobot n = hisobot.tozala(hisobot.oqi('sinov_malumot.csv')) print('jami qator:', n.jami, '| to\'g\'ri:', len(n.talabalar), '| xato:', len(n.xatolar)) print('guruh o\'rtachasi:', hisobot.guruh_ortachasi(n.talabalar)) print('darajalar:', hisobot.daraja_taqsimoti(n.talabalar)) print('birinchi talaba:', n.talabalar[0]) " .venv/bin/python3 -m hisobot sinov_malumot.csv
jami qator: 8 | to'g'ri: 6 | xato: 2
guruh o'rtachasi: {'A': 90.67, 'B': 83.0, 'C': 61.0}
darajalar: {'yaxshi': 3, "a'lo": 2, 'qoniqarli': 1}
birinchi talaba: Talaba(ism='Aziz', guruh='A', baho=85, sana='2026-09-01')
jami: 8, to'g'ri: 6, xato: 2
A: 90.67
B: 83.0
C: 61.0
darajalar: {'yaxshi': 3, "a'lo": 2, 'qoniqarli': 1}
5-qator (Elyor): baho son emas: ''
8-qator (Hasan): baho son emas: 'abc'
Xulosa
| Tushuncha | Asosiy fikr |
|---|---|
dict cheklovi |
Imlo xatosi jim o'tadi, tuzilish kafolatlanmaydi |
dataclass |
Bitta e'londan konstruktor, __repr__, __eq__ |
frozen=True |
O'zgartirishga urinish — FrozenInstanceError; replace() bilan yangi nusxa |
slots=True |
Noto'g'ri maydon nomi — AttributeError; xotira ham kamayadi |
Enum |
Cheklangan qiymatlar; noto'g'ri qiymat darhol ValueError |
str, Enum |
Matn kabi ishlaydi, JSON ga to'g'ridan-to'g'ri yoziladi |
| Chegara | Tashqi ma'lumot — dict; tekshiruvdan keyin — model |
Talaba ishi
Talabaga__post_init__qo'shing:ismbo'sh bo'lsaValueError.frozen=Truebilan bu ishlaydimi?Daraja.bahodanchegaralarini o'zgartiring (90/75/60 emas, 85/70/55). Qaysi fayllarga tegishingiz kerak bo'ldi?TalabaniNamedTupleqilib qayta yozing. Nima yo'qotdingiz, nima yutdingiz?- Yuqoridagi xotira o'lchovini 10 000 ta obyekt uchun takrorlang (
sys.getsizeofemas, ro'yxatning umumiy hajmi). Farq sezilarli bo'ldimi?
Bu bobni bepul o'qidingiz.
Kitob o'rgatadi, lekin sizning kodingiz nega ishlamaganini ayta olmaydi. Kursda uy vazifangiz tekshiriladi.