Dataclasses — @dataclass, field, frozen, __post_init__

~20 daq · Python · Ilg'or mavzular

Muammodan boshlaylik

Oddiy "ma'lumot saqlovchi" klass:

class Mahsulot:
    def __init__(self, nomi, narx, soni=0, teglar=None):
        self.nomi = nomi
        self.narx = narx
        self.soni = soni
        self.teglar = teglar if teglar is not None else []

    def __repr__(self):
        return f"Mahsulot(nomi={self.nomi!r}, narx={self.narx!r}, soni={self.soni!r}, teglar={self.teglar!r})"

    def __eq__(self, boshqa):
        if not isinstance(boshqa, Mahsulot):
            return NotImplemented
        return (self.nomi, self.narx, self.soni, self.teglar) == (boshqa.nomi, boshqa.narx, boshqa.soni, boshqa.teglar)

To'rtta maydon — 15 qator takrorlanuvchi kod. Har bir maydon nomi to'rt marta yozilgan. Yangi maydon qo'shilsa — to'rt joyda o'zgartirish, bittasini unutish oson.


@dataclass

from dataclasses import dataclass, field

@dataclass
class Mahsulot:
    nomi: str
    narx: int
    soni: int = 0
    teglar: list[str] = field(default_factory=list)

Bu 5 qator yuqoridagi 15 qatorning hammasini avtomatik yaratadi:

m = Mahsulot("Choynak", 120000, teglar=["idish"])
print(m)        # Mahsulot(nomi='Choynak', narx=120000, soni=0, teglar=['idish'])
print(m == Mahsulot("Choynak", 120000, teglar=["idish"]))   # True
m.soni += 5

@dataclass maydonlarni type hints (48-dars) dan oladi va yaratadi:

  • __init__ — barcha maydonlar bilan
  • __repr__ — o'qishli ko'rinish
  • __eq__ — maydonlar bo'yicha solishtirish

Hayotiy o'xshatma

Anketa blankasi bosmaxonasi. Siz faqat maydonlar ro'yxatini berasiz: "Ism, Yosh, Telefon". Bosmaxona o'zi chiroyli blankani, chiziqlarni va sarlavhalarni yaratadi. Har bir yangi anketa uchun chizg'ich bilan qo'lda chizish shart emas.


Standart qiymatlar va field

@dataclass
class Buyurtma:
    mijoz: str
    summa: int
    holat: str = "yangi"
    mahsulotlar: list[str] = field(default_factory=list)
    izoh: str = field(default="", repr=False)

[!WARNING] O'zgaruvchan standart qiymat to'g'ridan-to'g'ri yozilsa — @dataclass xato beradi: mahsulotlar: list = [] → ValueError: mutable default <class 'list'> for field mahsulotlar is not allowed: use default_factory. Bu 18-darsdagi tuzoqdan himoya: har bir ob'ekt uchun yangi ro'yxat — field(default_factory=list).

field() parametri Vazifasi
default Standart qiymat
default_factory Har safar yangi qiymat yaratuvchi funksiya (list, dict, datetime.now)
repr=False print da ko'rsatmaslik (parol, katta ma'lumot)
compare=False == da hisobga olmaslik
init=False __init__ parametri emas (ichkarida hisoblanadi)

Standart qiymatsiz maydonlar — oldin, standart qiymatlilar — keyin (funksiya parametrlari kabi, 18-dars).


__post_init__ — qo'shimcha tekshiruv va hisob

__init__ avtomatik yaratiladi — tekshiruvni qayerga yozish kerak?

from dataclasses import dataclass, field

@dataclass
class Mahsulot:
    nomi: str
    narx: int
    chegirma: float = 0.0
    yakuniy_narx: int = field(init=False)

    def __post_init__(self):
        self.nomi = self.nomi.strip()
        if self.narx < 0:
            raise ValueError("Narx manfiy bo'lishi mumkin emas")
        if not 0 <= self.chegirma <= 100:
            raise ValueError("Chegirma 0–100 oralig'ida bo'lsin")
        self.yakuniy_narx = round(self.narx * (1 - self.chegirma / 100))


m = Mahsulot("  Choynak ", 120000, chegirma=10)
print(m)     # Mahsulot(nomi='Choynak', narx=120000, chegirma=10, yakuniy_narx=108000)

Mahsulot("Kosa", -5)    # ValueError

__post_init__ — avtomatik __init__ tugagandan keyin chaqiriladi.


frozen — o'zgarmas

@dataclass(frozen=True)
class Nuqta:
    x: float
    y: float

p = Nuqta(3, 4)
p.x = 10          # ❌ FrozenInstanceError: cannot assign to field 'x'

joylar = {Nuqta(0, 0): "markaz"}    # ✅ hashable — dictionary kaliti bo'la oladi
  • Yaratilgandan keyin o'zgartirib bo'lmaydi (tuple kabi, 12-dars)
  • __hash__ avtomatik — set va dictionary kaliti sifatida ishlatsa bo'ladi
  • "Yangilash" — yangi nusxa:
from dataclasses import replace

p2 = replace(p, x=10)       # Nuqta(x=10, y=4)

Koordinatalar, pul summalari, sozlamalar kabi qiymat ob'ektlari uchun — frozen=True.


Boshqa opsiyalar

@dataclass(order=True)
class Talaba:
    reyting: float
    ism: str = field(compare=False)

talabalar = [Talaba(4.5, "Aziz"), Talaba(4.9, "Lola"), Talaba(4.1, "Bobur")]
print([t.ism for t in sorted(talabalar, reverse=True)])   # ['Lola', 'Aziz', 'Bobur']
Opsiya Nima qiladi
order=True <, >, <=, >= — maydonlar tartibida
frozen=True O'zgarmas + hashable
slots=True (3.10+) Kamroq xotira, tezroq atributlar
kw_only=True (3.10+) Barcha maydonlar faqat nom bilan: Mahsulot(nomi=..., narx=...)

Metodlar va meros

Dataclass — oddiy klass: metodlar, property lar, meros — hammasi ishlaydi:

@dataclass
class BankHisobi:
    egasi: str
    balans: int = 0

    def pul_qosh(self, summa: int) -> None:
        self.balans += summa

    @property
    def formatlangan(self) -> str:
        return f"{self.balans:,} so'm"


@dataclass
class OmonatHisobi(BankHisobi):
    stavka: float = 0.14

Dictionary va JSON ga

from dataclasses import asdict, astuple
import json

m = Mahsulot("Choynak", 120000, teglar=["idish"])
print(asdict(m))     # {'nomi': 'Choynak', 'narx': 120000, 'soni': 0, 'teglar': ['idish']}
print(json.dumps(asdict(m), ensure_ascii=False))

d = {"nomi": "Kosa", "narx": 30000}
m2 = Mahsulot(**d)   # dictionary dan (19-dars — ** yoyish)

dataclass, namedtuple yoki pydantic?

namedtuple @dataclass pydantic.BaseModel
O'zgaruvchan ❌ ✅ (yoki frozen) ✅
Standart kutubxona ✅ ✅ ❌ (tashqi)
Turlarni ishlash paytida tekshiradi ❌ ❌ ✅
Tur o'girish ("25" → 25) ❌ ❌ ✅
Qachon Oddiy o'zgarmas yozuv Ilova ichidagi ma'lumot ob'ektlari Tashqi ma'lumot: API, JSON, formalar

[!INFO] @dataclass type hints ni tekshirmaydi: Mahsulot("Choynak", "arzon") xatosiz yaratiladi. Tashqi manbadan kelgan ma'lumotni tekshirish kerak bo'lsa — __post_init__ da tekshiruv yoki pydantic (FastAPI ichida ishlatiladigan kutubxona).


Tez-tez uchraydigan xatolar

Type hint siz maydon:

@dataclass
class A:
    x = 5        # ❌ maydon emas — oddiy class atributi, __init__ ga kirmaydi
    x: int = 5   # ✅

O'zgaruvchan default — field(default_factory=list).

Standart qiymatli maydondan keyin standart qiymatsiz:

@dataclass
class B:
    a: int = 0
    b: str          # ❌ TypeError: non-default argument 'b' follows default argument

Xulosa

@dataclass — ma'lumot saqlovchi klasslar uchun takrorlanuvchi kodni avtomatik yaratadi.

Eslab qoling:

  • @dataclass + type hints → __init__, __repr__, __eq__
  • Ro'yxat/dict standart qiymat — field(default_factory=list)
  • field(repr=False, compare=False, init=False)
  • __post_init__ — tekshiruv va hisoblangan maydonlar
  • frozen=True — o'zgarmas, hashable; replace() — yangi nusxa
  • order=True, slots=True, kw_only=True
  • asdict() — JSON uchun; turlar tekshirilmaydi — kerak bo'lsa pydantic

Keyingi dars

50-dars: logging — level, handler, formatter, haqiqiy loyihada qanday ishlatiladi