Asosiy qismga o'tish

Ma'lumot tuzilmalari: lug'atdan dataclass gacha

4-bob 17 kod bloki 2397 so'z

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.

python14 qator
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")
Chiqish
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

python45 qator
"""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)
python7 qator
.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'))
"
Chiqish (qaytish kodi 0, 92 ms)
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

python5 qator
.venv/bin/python3 -c "
from hisobot.modellar import Talaba
t = Talaba('Aziz', 'A', 85, '2026-09-01')
t.baho = 100
"
Chiqish (qaytish kodi 1, 76 ms)
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:

python9 qator
.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)
"
Chiqish (qaytish kodi 0, 69 ms)
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:

python21 qator
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)
Chiqish
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:

python12 qator
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}")
Chiqish
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:

python50 qator
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)},
]))
turbayt
dict232
dataclass152
dataclass(slots=True)64
amaldict, mksdataclass, mksslots, mks
yaratish0.0690.120.099
maydon o'qish0.0250.0260.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:

python6 qator
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")
Chiqish
matn bilan: boshqa   <- jim noto'g'ri javob

Enum bilan bunday xato darhol chiqadi:

python9 qator
.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)
"
Chiqish (qaytish kodi 0, 465 ms)
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:

python6 qator
.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')
"
Chiqish (qaytish kodi 0, 98 ms)
{"daraja": "a'lo"}
matn sifatida solishtirish: True

Qaysi tuzilmani qachon

python9 qator
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)"},
]))
tuzilmaqachon
dictTuzilishi oldindan noma'lum; JSON dan endi o'qilgan xom ma'lumot
dataclassIchki modellar — maydonlar ma'lum va barqaror
dataclass(frozen)Ma'lumot funksiyalar orasida yuradi; o'zgarmasligi kerak
NamedTupleKichik, indeks bilan ham murojaat qilinadi; to'liq o'zgarmas
EnumQiymatlar to'plami cheklangan: holat, daraja, tur
pydantic.BaseModelTashqi 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

python27 qator
"""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

python14 qator
"""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

python24 qator
"""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

python10 qator
"""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__"]
python9 qator
.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
Chiqish (qaytish kodi 1, 170 ms)
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

  1. Talaba ga __post_init__ qo'shing: ism bo'sh bo'lsa ValueError. frozen=True bilan bu ishlaydimi?
  2. Daraja.bahodan chegaralarini o'zgartiring (90/75/60 emas, 85/70/55). Qaysi fayllarga tegishingiz kerak bo'ldi?
  3. Talaba ni NamedTuple qilib qayta yozing. Nima yo'qotdingiz, nima yutdingiz?
  4. Yuqoridagi xotira o'lchovini 10 000 ta obyekt uchun takrorlang (sys.getsizeof emas, 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.

Telegramda yozish Narxlar