IlmHamroh
Python kursi/OOP3/20-dars35 daqiqa
Mundarija (22)

8.3-dars: __init__ va atributlar

8-QISM — OBYEKTGA YO'NALTIRILGAN DASTURLASH · 3-dars


1. Kirish va motivatsiya

Ikki __init__, bir xil maqsad:

python
class Buyurtma:
    def __init__(self, mahsulotlar=[], chegirma=0, mijoz=None):
        self.mahsulotlar = mahsulotlar
        self.chegirma = chegirma
        self.mijoz = mijoz
python
class Buyurtma:
    def __init__(
        self,
        mijoz: str,
        mahsulotlar: list[str] | None = None,
        chegirma: float = 0.0,
    ):
        if not mijoz.strip():
            raise ValueError("Mijoz ko'rsatilsin")
        if not 0 <= chegirma <= 100:
            raise ValueError(f"Chegirma 0–100: {chegirma}")

        self.mijoz = mijoz.strip()
        self.mahsulotlar = list(mahsulotlar or [])
        self.chegirma = chegirma
        self._yaratilgan = datetime.now()
        self._holat = "yangi"

Birinchisidagi to'rt xato:

python
b1 = Buyurtma()
b1.mahsulotlar.append("non")
b2 = Buyurtma()
b2.mahsulotlar                  # ['non'] ⚠️ umumiy sukut ro'yxat!

b3 = Buyurtma(chegirma=500)     # ⚠️ tekshirilmadi
b4 = Buyurtma(mijoz="")         # ⚠️ bo'sh mijoz
b5 = Buyurtma(["non"])          # ⚠️ mijoz o'rniga ro'yxat

Bu darsda:

  • Sukut argument tuzog'i — sinflarda ham
  • Atribut nomlash va e'lon qilish
  • Invariant va uni qaerda tekshirish
  • Hosila atributlar va @property
  • __post_init__, fabrika metodlari
  • Nusxa atributlarini bir joyda e'lon qilish

2. Nazariya — chuqur tushuntirish

2.1. __init__ ning vazifasi

python
def __init__(self, ...) -> None:
    """Obyektni ISHLATISHGA TAYYOR holatga keltiradi."""

To'rt vazifa:

  1. Argumentlarni tekshirish — noto'g'ri obyekt yaratilmasin
  2. Normallashtirish — strip(), tur o'girish, nusxa olish
  3. Atributlarni o'rnatish — barchasi shu yerda
  4. Boshlang'ich holat — hisoblagichlar, keshlar, bayroqlar

__init__ da BO'LMASLIGI kerak:

python
def __init__(self, url):
    self.malumot = tarmoqdan_yukla(url)     # ⚠️ sekin, xato berishi mumkin
    self.fayl = open("a.txt")               # ⚠️ resurs — with kerak
    print("Yaratildi")                      # ⚠️ yon ta'sir
    global JAMI; JAMI += 1                  # ⚠️ global holat

Og'ir ish uchun fabrika metodi yoki kechiktirilgan yuklash:

python
class Malumot:
    def __init__(self, xom: dict):
        self.xom = xom                      # tez, ishonchli

    @classmethod
    def urldan(cls, url: str) -> "Malumot": # ✅ aniq
        return cls(tarmoqdan_yukla(url))

2.2. Sukut argument tuzog'i — sinflarda

7.3-darsda ko'rgan tuzoq bu yerda ikki xil ko'rinishda uchraydi:

A) __init__ sukut argumenti (funksiya bilan bir xil):

python
class A:
    def __init__(self, lst=[]):     # ⚠️ BIR MARTA yaratiladi
        self.lst = lst

a, b = A(), A()
a.lst.append(1)
b.lst                               # [1] ⚠️
a.lst is b.lst                      # True

B) Sinf atributi (8.2-darsda ko'rgan):

python
class A:
    lst = []                        # ⚠️ hamma nusxaga umumiy

a, b = A(), A()
a.lst.append(1)
b.lst                               # [1] ⚠️

Ikkalasining yechimi bir xil:

python
class A:
    def __init__(self, lst: list | None = None):
        self.lst = list(lst) if lst is not None else []

list(lst) — nusxa oladi. Bu ikkinchi himoya:

python
class B:
    def __init__(self, lst):
        self.lst = lst              # ⚠️ tashqi ro'yxatga havola

tashqi = [1, 2]
b = B(tashqi)
tashqi.append(3)
b.lst                               # [1, 2, 3] ⚠️ obyekt ichi o'zgardi

Qachon nusxa olish kerak:

Holat Nusxa Sabab
Ro'yxat/lug'at/to'plam saqlaymiz Tashqi o'zgarishdan himoya
Faqat o'qiymiz va qaytarmaymiz Ixtiyoriy
Juda katta konteyner Xotira; hujjatda ayting
O'zgarmas (tuple, frozenset) Kerak emas

2.3. Atributlarni e'lon qilish

Barcha nusxa atributlari __init__ da paydo bo'lsin:

python
class Yomon:                        # ⚠️
    def __init__(self, x):
        self.x = x

    def hisobla(self):
        self.natija = self.x * 2    # ⚠️ atribut shu yerda tug'ildi

y = Yomon(5)
y.natija                            # AttributeError — hali chaqirilmagan
python
class Yaxshi:                       # ✅
    def __init__(self, x):
        self.x = x
        self.natija: int | None = None      # aniq e'lon

    def hisobla(self):
        self.natija = self.x * 2

Foydasi: IDE biladi, mypy biladi, o'quvchi biladi.

Nomlash:

Prefiks Ma'nosi Misol
nom Ommaviy self.ism
_nom Ichki (kelishuv) self._kesh
__nom Nom buzilishi (8.10) self.__sirli
NOM Konstanta (sinf darajasida) MAKS = 100

2.4. Invariant — qaerda tekshirish

Invariant — obyekt hayoti davomida doim rost bo'lishi kerak bo'lgan shart.

python
class Hisob:
    """Invariant: balans >= 0."""

    def __init__(self, balans: float = 0):
        if balans < 0:                          # 1️⃣ yaratishda
            raise ValueError("Manfiy bo'lmasin")
        self._balans = balans

    def yech(self, summa: float) -> None:
        if summa <= 0:
            raise ValueError("Musbat bo'lsin")
        if summa > self._balans:                # 2️⃣ har o'zgarishda
            raise ValueError("Mablag' yetarli emas")
        self._balans -= summa

Uch qoida:

  1. __init__ da tekshiring — noto'g'ri obyekt umuman yaratilmasin
  2. Har o'zgartiruvchi metodda tekshiring — buzilmasin
  3. Ichki holatni himoyalang — _balans ga to'g'ridan-to'g'ri yozilmasin

Tekshiruvni takrorlamang — bitta joyga yig'ing:

python
class Hisob:
    def __init__(self, balans=0):
        self._balans = 0
        self.qosh(balans)               # ✅ qayta ishlatildi

    def qosh(self, summa):
        if summa < 0:
            raise ValueError("Manfiy bo'lmasin")
        self._balans += summa

2.5. Hosila atributlar

Hisoblanadigan qiymatni saqlamang:

python
class Togriturtburchak:
    def __init__(self, a, b):
        self.a, self.b = a, b
        self.yuza = a * b               # ⚠️ eskiradi!

t = Togriturtburchak(3, 4)
t.a = 10
t.yuza                                  # 12 ⚠️ noto'g'ri

@property bilan har safar hisoblang:

python
class Togriturtburchak:
    def __init__(self, a, b):
        self.a, self.b = a, b

    @property
    def yuza(self):
        return self.a * self.b          # ✅ doim to'g'ri

Qachon saqlash o'rinli:

python
from functools import cached_property

class Hujjat:
    def __init__(self, matn):
        self.matn = matn                # o'zgarmasligi kutiladi

    @cached_property
    def sozlar(self):
        """Bir marta hisoblanadi, keyin keshdan."""
        return self.matn.split()

cached_property faqat asos o'zgarmasa xavfsiz.

2.6. Fabrika metodlari

__init__ — bitta yo'l. Boshqa yo'llar uchun @classmethod:

python
class Sana:
    def __init__(self, kun: int, oy: int, yil: int):
        ...

    @classmethod
    def satrdan(cls, s: str) -> "Sana":
        kun, oy, yil = map(int, s.split("."))
        return cls(kun, oy, yil)

    @classmethod
    def bugun(cls) -> "Sana":
        d = date.today()
        return cls(d.day, d.month, d.year)


Sana(15, 3, 2026)
Sana.satrdan("15.03.2026")
Sana.bugun()

Nega fabrika, __init__ da tur tekshiruvi emas:

python
# ⚠️ Yomon
def __init__(self, malumot):
    if isinstance(malumot, str):
        ...
    elif isinstance(malumot, tuple):
        ...
    elif isinstance(malumot, date):
        ...

# ✅ Yaxshi — har yo'l alohida nom, alohida hujjat
Sana.satrdan(...)
Sana.tupledan(...)
Sana.datedan(...)

Fabrika metodlari 8.16-darsda batafsil.

2.7. dataclass — avtomatik __init__

python
from dataclasses import dataclass, field

@dataclass
class Nuqta:
    x: float
    y: float = 0.0
    teglar: list[str] = field(default_factory=list)   # ⭐ sukut tuzog'i yo'q

    def __post_init__(self):
        """⭐ __init__ dan keyin — tekshiruv shu yerda."""
        if self.x < 0:
            raise ValueError("x manfiy bo'lmasin")

Bepul olinadi: __init__, __repr__, __eq__.

field(default_factory=...) — majburiy:

python
@dataclass
class A:
    lst: list = []                      # ❌ ValueError yaratishda
    lst: list = field(default_factory=list)     # ✅

dataclass sukut tuzog'ini taqiqlaydi — bu uning katta afzalligi.

To'liq dataclass — 8.17-darsda.

2.8. __init__ va meros

python
class Ota:
    def __init__(self, a):
        self.a = a

class Bola(Ota):
    def __init__(self, a, b):
        super().__init__(a)             # ⭐ ota-sinfni chaqirish
        self.b = b

super().__init__() unutish — eng ko'p uchraydigan meros xatosi:

python
class Bola(Ota):
    def __init__(self, a, b):
        self.b = b                      # ⚠️ self.a yo'q

Bola(1, 2).a                            # AttributeError

super() va MRO — 8.7 va 8.8-darslarda.


3. Tez ma'lumotnoma

__init__ shabloni

python
class Nom:
    KONSTANTA = 10

    def __init__(
        self,
        majburiy: str,
        ixtiyoriy: int = 0,
        konteyner: list[str] | None = None,     # ⭐ None, [] emas
    ) -> None:
        # 1. Tekshirish
        if not majburiy.strip():
            raise ValueError(...)
        # 2. Normallashtirish
        self.majburiy = majburiy.strip()
        # 3. Nusxa olish
        self.konteyner = list(konteyner or [])
        # 4. Ichki holat
        self._kesh: dict = {}
        self._holat = "yangi"

Sukut tuzoqlari

python
def __init__(self, lst=[]):     ❌ bir marta yaratiladi
class A: lst = []               ❌ hamma nusxaga umumiy
def __init__(self, lst=None):   ✅
    self.lst = list(lst or [])  ✅ nusxa ham oladi

@dataclass
lst: list = []                  ❌ ValueError
lst: list = field(default_factory=list)   ✅

Invariant

1. __init__ da tekshiring        noto'g'ri obyekt yaratilmasin
2. Har o'zgartirishda            buzilmasin
3. Ichkarini himoyalang          _ prefiks, property
4. Tekshiruvni bitta joyga       __init__ metodni chaqirsin

Hosila qiymat

python
self.yuza = a * b               ⚠️ eskiradi
@property
def yuza(self): return ...      ✅ doim to'g'ri
@cached_property                ✅ asos o'zgarmasa

Fabrika

python
@classmethod
def satrdan(cls, s): return cls(...)

4. Batafsil misollar

Misol 1 — Sukut argument tuzoqlari

python
"""Sinflardagi sukut qiymat tuzoqlarining barcha ko'rinishi."""

from dataclasses import dataclass, field

print("=== ⚠️ TUZOQ 1: __init__ sukut argumenti ===\n")


class Savat1:
    def __init__(self, mahsulotlar=[]):     # ⚠️
        self.mahsulotlar = mahsulotlar

    def qosh(self, m):
        self.mahsulotlar.append(m)


a, b = Savat1(), Savat1()
a.qosh("non")
b.qosh("sut")

print(f"  a.mahsulotlar = {a.mahsulotlar}")
print(f"  b.mahsulotlar = {b.mahsulotlar}")
print(f"  a.mahsulotlar is b.mahsulotlar = {a.mahsulotlar is b.mahsulotlar}")
print(f"\n  Sukut obyekt id: "
      f"{id(Savat1.__init__.__defaults__[0]) % 100000}")
print(f"  a.mahsulotlar id: {id(a.mahsulotlar) % 100000}")
print(f"  ⚠️ BIR XIL — sukut ro'yxat def paytida BIR MARTA yaratilgan")


print("\n=== ⚠️ TUZOQ 2: sinf atributi ===\n")


class Savat2:
    mahsulotlar = []                        # ⚠️

    def qosh(self, m):
        self.mahsulotlar.append(m)          # ⚠️ sinf ro'yxatini o'zgartiradi


c, d = Savat2(), Savat2()
c.qosh("non")
d.qosh("sut")

print(f"  c.mahsulotlar        = {c.mahsulotlar}")
print(f"  d.mahsulotlar        = {d.mahsulotlar}")
print(f"  Savat2.mahsulotlar   = {Savat2.mahsulotlar}")
print(f"  c.__dict__           = {c.__dict__}   ← BO'SH!")
print(f"\n  ⚠️ append() nusxaga YOZMAYDI — sinf ro'yxatini o'zgartiradi")


print("\n=== ⚠️ TUZOQ 3: tashqi obyektga havola ===\n")


class Savat3:
    def __init__(self, mahsulotlar=None):
        self.mahsulotlar = mahsulotlar or []    # ✅ tuzoq 1 hal bo'ldi
                                                # ⚠️ lekin nusxa olinmadi


tashqi = ["non", "sut"]
e = Savat3(tashqi)
print(f"  tashqi = {tashqi}")
print(f"  e = Savat3(tashqi)")
print(f"  e.mahsulotlar = {e.mahsulotlar}")

tashqi.append("shakar")
print(f"\n  tashqi.append('shakar') dan keyin:")
print(f"    e.mahsulotlar = {e.mahsulotlar}   ⚠️ obyekt ichi o'zgardi!")
print(f"    e.mahsulotlar is tashqi = {e.mahsulotlar is tashqi}")


print("\n=== ⚠️ TUZOQ 4: `or` bilan bo'sh qiymat ===\n")


class Sozlama1:
    def __init__(self, chegara=None, teglar=None, nom=None):
        self.chegara = chegara or 100           # ⚠️
        self.teglar = teglar or []
        self.nom = nom or "sukut"


s = Sozlama1(chegara=0, teglar=[], nom="")

print(f"  Sozlama1(chegara=0, teglar=[], nom='')")
print(f"    s.chegara = {s.chegara}   ⚠️ 0 kutilgan edi!")
print(f"    s.teglar  = {s.teglar}")
print(f"    s.nom     = {s.nom!r}  ⚠️ '' kutilgan edi!")

print(f"""
  ⚠️ SABAB: `or` FALSY qiymatlarni almashtiradi:
     0, 0.0, "", [], {{}}, set(), None, False

  ✅ YECHIM: `is None` bilan aniq tekshirish
""")


class Sozlama2:
    def __init__(self, chegara=None, teglar=None, nom=None):
        self.chegara = 100 if chegara is None else chegara      # ✅
        self.teglar = [] if teglar is None else list(teglar)    # ✅
        self.nom = "sukut" if nom is None else nom              # ✅


s2 = Sozlama2(chegara=0, teglar=[], nom="")
print(f"  Sozlama2(chegara=0, teglar=[], nom='')")
print(f"    s2.chegara = {s2.chegara}   ✅")
print(f"    s2.nom     = {s2.nom!r}  ✅")


print("\n=== ✅ TO'G'RI YECHIM ===\n")


class Savat:
    """Barcha tuzoqlar hal qilingan."""

    MAKS = 50

    def __init__(
        self,
        mijoz: str,
        mahsulotlar: list[str] | None = None,
        chegirma: float = 0.0,
    ) -> None:
        # 1. Tekshirish
        if not mijoz.strip():
            raise ValueError("Mijoz nomi bo'sh bo'lmasin")
        if not 0 <= chegirma <= 100:
            raise ValueError(f"Chegirma 0–100 oralig'ida: {chegirma}")

        # 2. Normallashtirish + 3. Nusxa olish
        self.mijoz = mijoz.strip()
        self._mahsulotlar: list[str] = list(mahsulotlar or [])  # ⭐ nusxa
        self.chegirma = chegirma

        # 4. Ichki holat — hammasi shu yerda e'lon qilingan
        self._yopilgan = False
        self._jami_kesh: float | None = None

        if len(self._mahsulotlar) > self.MAKS:
            raise ValueError(f"Ko'pi bilan {self.MAKS} mahsulot")

    @property
    def mahsulotlar(self) -> tuple[str, ...]:
        """Nusxa — tashqaridan o'zgartirib bo'lmaydi."""
        return tuple(self._mahsulotlar)

    def qosh(self, m: str) -> None:
        if self._yopilgan:
            raise RuntimeError("Savat yopilgan")
        if len(self._mahsulotlar) >= self.MAKS:
            raise ValueError(f"Ko'pi bilan {self.MAKS} mahsulot")
        self._mahsulotlar.append(m)
        self._jami_kesh = None                  # kesh eskirdi

    def __repr__(self):
        return (f"Savat({self.mijoz!r}, {len(self._mahsulotlar)} mahsulot, "
                f"chegirma={self.chegirma})")


print(f"  Alohida nusxalar:")
x, y = Savat("Aziz"), Savat("Malika")
x.qosh("non")
y.qosh("sut")
print(f"    x = {x}, mahsulotlar={x.mahsulotlar}")
print(f"    y = {y}, mahsulotlar={y.mahsulotlar}")
print(f"    ✅ ajralgan: {x.mahsulotlar != y.mahsulotlar}")

print(f"\n  Tashqi o'zgarishdan himoya:")
tashqi = ["non", "sut"]
z = Savat("Bobur", tashqi)
tashqi.append("shakar")
print(f"    tashqi = {tashqi}")
print(f"    z.mahsulotlar = {z.mahsulotlar}   ✅ o'zgarmadi")

print(f"\n  Ichkarini o'zgartirishdan himoya:")
try:
    z.mahsulotlar.append("qand")
except AttributeError as e:
    print(f"    z.mahsulotlar.append(...) → ✅ AttributeError (tuple)")

print(f"\n  Invariant:")
for tavsif, f in [
    ("Savat('')",        lambda: Savat("")),
    ("chegirma=200",     lambda: Savat("A", chegirma=200)),
    ("chegirma=-5",      lambda: Savat("A", chegirma=-5)),
    ("60 mahsulot",      lambda: Savat("A", ["x"] * 60)),
    ("chegirma=0",       lambda: Savat("A", chegirma=0)),
]:
    try:
        f(); n = "✅ o'tdi"
    except ValueError as err:
        n = f"✅ ValueError: {err}"
    print(f"    {tavsif:<18} {n}")


print("\n=== ⭐ dataclass tuzoqni TAQIQLAYDI ===\n")

print("""  @dataclass
  class A:
      lst: list = []            ❌ ValueError yaratish PAYTIDA:
                                   mutable default <class 'list'> for
                                   field lst is not allowed
""")

try:
    @dataclass
    class Xato:
        lst: list = []
except ValueError as e:
    print(f"  Haqiqiy xato: ValueError: {str(e)[:70]}...")


@dataclass
class Togri:
    nom: str
    lst: list[str] = field(default_factory=list)        # ✅
    kesh: dict = field(default_factory=dict, repr=False) # ✅ repr da yo'q

    def __post_init__(self):
        if not self.nom.strip():
            raise ValueError("Nom bo'sh bo'lmasin")
        self.nom = self.nom.strip()


t1, t2 = Togri("A"), Togri("B")
t1.lst.append("x")
print(f"\n  ✅ t1 = {t1}")
print(f"  ✅ t2 = {t2}")
print(f"     t1.lst is t2.lst = {t1.lst is t2.lst}")

try:
    Togri("   ")
except ValueError as e:
    print(f"\n  __post_init__ tekshiruvi: ✅ ValueError: {e}")

print(f"""
  ⭐ dataclass afzalliklari:
     • Sukut tuzog'ini TAQIQLAYDI (ValueError)
     • __init__, __repr__, __eq__ bepul
     • __post_init__ da tekshiruv
     • field(repr=False) — sirlarni yashirish
""")

Natijaning muhim qismi:

text
=== ⚠️ TUZOQ 1: __init__ sukut argumenti ===

  a.mahsulotlar = ['non', 'sut']
  b.mahsulotlar = ['non', 'sut']
  a.mahsulotlar is b.mahsulotlar = True

=== ⚠️ TUZOQ 4: `or` bilan bo'sh qiymat ===

  Sozlama1(chegara=0, teglar=[], nom='')
    s.chegara = 100   ⚠️ 0 kutilgan edi!
    s.nom     = 'sukut'  ⚠️ '' kutilgan edi!

  Sozlama2(chegara=0, teglar=[], nom='')
    s2.chegara = 0   ✅

=== ✅ TO'G'RI YECHIM ===

  Tashqi o'zgarishdan himoya:
    tashqi = ['non', 'sut', 'shakar']
    z.mahsulotlar = ('non', 'sut')   ✅ o'zgarmadi

Nima ko'rsatdi: 2.2-bo'lim.

Misol 2 — Invariant loyihalash

python
"""Invariantni qanday aniqlash va himoyalash."""

from datetime import date, datetime

print("=== 1. Invariantsiz sinf ===\n")


class HisobYomon:
    def __init__(self, egasi, balans=0):
        self.egasi = egasi
        self.balans = balans

    def yech(self, summa):
        self.balans -= summa


h = HisobYomon("Aziz", 100)

BUZISHLAR = [
    ("h.yech(1000)",        lambda: h.yech(1000)),
    ("h.balans = -999",     lambda: setattr(h, "balans", -999)),
    ("h.yech(-50)",         lambda: h.yech(-50)),
    ("h.egasi = None",      lambda: setattr(h, "egasi", None)),
    ("h.balans = 'abc'",    lambda: setattr(h, "balans", "abc")),
]

for kod, f in BUZISHLAR:
    f()
    print(f"  {kod:<22} → balans={h.balans!r}, egasi={h.egasi!r}")

print(f"\n  ⚠️ Hech qanday himoya yo'q — obyekt istalgan holatga tushdi")


print("\n=== 2. ✅ Invariant bilan ===\n")


class Hisob:
    """Bank hisobi.

    INVARIANTLAR:
      1. balans >= 0 (doim)
      2. egasi — bo'sh bo'lmagan satr
      3. tarix faqat o'sadi, o'chirilmaydi
      4. yopilgan hisobda amal bajarilmaydi
    """

    ENG_KAM_QOLDIQ = 0.0
    ENG_KOP_AMAL = 1_000_000_000.0

    def __init__(self, egasi: str, balans: float = 0.0) -> None:
        # Barcha atributlar shu yerda e'lon qilinadi
        self._egasi = ""
        self._balans = 0.0
        self._tarix: list[tuple[datetime, str, float, float]] = []
        self._yopilgan = False

        self.egasi = egasi                  # setter tekshiradi
        if balans:
            self.qosh(balans, "boshlang'ich")

    # ── Xususiyatlar ──

    @property
    def egasi(self) -> str:
        return self._egasi

    @egasi.setter
    def egasi(self, qiymat: str) -> None:
        """⭐ Invariant 2 — bitta joyda."""
        if not isinstance(qiymat, str):
            raise TypeError(f"Egasi satr bo'lsin: {type(qiymat).__name__}")
        if not qiymat.strip():
            raise ValueError("Egasi bo'sh bo'lmasin")
        self._egasi = qiymat.strip()

    @property
    def balans(self) -> float:
        """⭐ Faqat o'qish — setter YO'Q."""
        return self._balans

    @property
    def tarix(self) -> tuple:
        return tuple(self._tarix)

    @property
    def yopilgan(self) -> bool:
        return self._yopilgan

    # ── Amallar ──

    def _tekshir(self, summa: float) -> float:
        """⭐ Umumiy tekshiruv — takrorlanmaydi."""
        if self._yopilgan:
            raise RuntimeError("Hisob yopilgan")
        if not isinstance(summa, (int, float)) or isinstance(summa, bool):
            raise TypeError(f"Summa son bo'lsin: {type(summa).__name__}")
        if summa <= 0:
            raise ValueError(f"Summa musbat bo'lsin: {summa}")
        if summa > self.ENG_KOP_AMAL:
            raise ValueError(f"Juda katta summa: {summa}")
        return float(summa)

    def _yoz(self, amal: str, summa: float) -> None:
        self._tarix.append((datetime.now(), amal, summa, self._balans))

    def qosh(self, summa: float, izoh: str = "kirim") -> None:
        summa = self._tekshir(summa)
        self._balans += summa
        self._yoz(izoh, summa)

    def yech(self, summa: float) -> None:
        summa = self._tekshir(summa)
        yangi = self._balans - summa
        if yangi < self.ENG_KAM_QOLDIQ:                 # ⭐ Invariant 1
            raise ValueError(
                f"Mablag' yetarli emas: {self._balans:.2f} da "
                f"{summa:.2f} yechib bo'lmaydi"
            )
        self._balans = yangi
        self._yoz("chiqim", -summa)

    def otkaz(self, boshqa: "Hisob", summa: float) -> None:
        """⭐ Atomik: xato bo'lsa hech nima o'zgarmaydi."""
        if boshqa is self:
            raise ValueError("O'ziga o'tkazib bo'lmaydi")
        self.yech(summa)                    # avval tekshiradi
        try:
            boshqa.qosh(summa, f"{self.egasi} dan")
        except Exception:
            self.qosh(summa, "qaytarildi")  # orqaga qaytarish
            raise

    def yop(self) -> None:
        if self._balans:
            raise RuntimeError(f"Balans nol emas: {self._balans:.2f}")
        self._yopilgan = True

    def __repr__(self) -> str:
        holat = " YOPIQ" if self._yopilgan else ""
        return f"Hisob({self._egasi!r}, {self._balans:.2f}{holat})"


print("  To'g'ri ishlatish:\n")
a = Hisob("Aziz", 500_000)
b = Hisob("Malika")

a.qosh(200_000, "maosh")
a.yech(50_000)
a.otkaz(b, 100_000)

print(f"    {a}")
print(f"    {b}")

print(f"\n  a ning tarixi:")
for vaqt, amal, summa, oldingi in a.tarix:
    print(f"    {vaqt:%H:%M:%S}  {amal:<18} {summa:>+14,.0f}  "
          f"(oldin: {oldingi:>12,.0f})")

print(f"\n  Invariant himoyasi:\n")

HIMOYA = [
    ("a.yech(99_999_999)",  lambda: a.yech(99_999_999)),
    ("a.yech(-100)",        lambda: a.yech(-100)),
    ("a.yech(0)",           lambda: a.yech(0)),
    ("a.yech('abc')",       lambda: a.yech("abc")),
    ("a.yech(True)",        lambda: a.yech(True)),
    ("a.balans = 999",      lambda: setattr(a, "balans", 999)),
    ("a.egasi = ''",        lambda: setattr(a, "egasi", "")),
    ("a.egasi = 42",        lambda: setattr(a, "egasi", 42)),
    ("a.tarix.clear()",     lambda: a.tarix.clear()),
    ("a.otkaz(a, 100)",     lambda: a.otkaz(a, 100)),
    ("a.yop() (balans>0)",  lambda: a.yop()),
]

for kod, f in HIMOYA:
    try:
        f()
        natija = "⚠️ O'TDI"
    except Exception as e:
        natija = f"✅ {type(e).__name__}: {str(e)[:44]}"
    print(f"    {kod:<24} {natija}")

print(f"\n  Yakuniy holat: {a}   ← invariant buzilmadi")


print("\n\n=== 3. Atomik amal ===\n")


class NosozHisob(Hisob):
    """Test uchun — qosh() doim xato beradi."""
    def qosh(self, summa, izoh="kirim"):
        if izoh != "qaytarildi":
            raise RuntimeError("Tarmoq xatosi")
        super().qosh(summa, izoh)


c = Hisob("Bobur", 300_000)
n = NosozHisob("Nosoz")

print(f"  Oldin:  {c}")
try:
    c.otkaz(n, 100_000)
except RuntimeError as e:
    print(f"  Xato:   RuntimeError: {e}")
print(f"  Keyin:  {c}   ✅ pul qaytarildi")

print(f"""
  ⭐ ATOMIKLIK: amal to'liq bajariladi yoki umuman bajarilmaydi.

     try:
         boshqa.qosh(summa)
     except Exception:
         self.qosh(summa, "qaytarildi")     # orqaga qaytarish
         raise
""")


print("=== 4. Invariantni hujjatlash ===\n")

print(f"  {Hisob.__doc__}")

print("""
  ⭐ INVARIANTNI ANIQLASH SAVOLLARI:

     1. Bu atribut qanday qiymatlarni QABUL QILMASLIGI kerak?
        → balans < 0, egasi = ""

     2. Ikki atribut o'rtasida bog'liqlik bormi?
        → boshlanish <= tugash

     3. Qaysi amal qaysi holatda TAQIQLANGAN?
        → yopilgan hisobda yech()

     4. Nimani tashqaridan o'zgartirib BO'LMASLIGI kerak?
        → tarix, balans (to'g'ridan-to'g'ri)

     5. Xato o'rtada sodir bo'lsa nima bo'ladi?
        → orqaga qaytarish (atomiklik)
""")

Natijaning muhim qismi:

text
=== 1. Invariantsiz sinf ===

  h.yech(1000)           → balans=-900, egasi='Aziz'
  h.balans = -999        → balans=-999, egasi='Aziz'
  h.balans = 'abc'       → balans='abc', egasi=None

=== 2. ✅ Invariant bilan ===

    Hisob('Aziz', 550000.00)
    Hisob('Malika', 100000.00)

  Invariant himoyasi:

    a.yech(99_999_999)       ✅ ValueError: Mablag' yetarli emas
    a.yech(-100)             ✅ ValueError: Summa musbat bo'lsin: -100
    a.yech(True)             ✅ TypeError: Summa son bo'lsin: bool
    a.balans = 999           ✅ AttributeError: property 'balans' of 'Hisob' object has no s
    a.tarix.clear()          ✅ AttributeError: 'tuple' object has no...
    a.otkaz(a, 100)          ✅ ValueError: O'ziga o'tkazib bo'lmaydi

=== 3. Atomik amal ===

  Oldin:  Hisob('Bobur', 300000.00)
  Xato:   RuntimeError: Tarmoq xatosi
  Keyin:  Hisob('Bobur', 300000.00)   ✅ pul qaytarildi

Nima ko'rsatdi: 2.4-bo'lim.

Misol 3 — Hosila atributlar

python
"""Hisoblanadigan qiymatlarni qanday saqlash (yoki saqlamaslik)."""

import time
from functools import cached_property

print("=== ⚠️ 1. Saqlangan hosila qiymat eskiradi ===\n")


class TogriturtburchakYomon:
    def __init__(self, a, b):
        self.a, self.b = a, b
        self.yuza = a * b               # ⚠️
        self.perimetr = 2 * (a + b)     # ⚠️


t = TogriturtburchakYomon(3, 4)
print(f"  Boshida:  a={t.a}, b={t.b}, yuza={t.yuza}, perimetr={t.perimetr}")

t.a = 10
print(f"  a=10:     a={t.a}, b={t.b}, yuza={t.yuza} ⚠️, "
      f"perimetr={t.perimetr} ⚠️")
print(f"  Kutilgan: yuza=40, perimetr=28")


print("\n=== ✅ 2. @property — har safar hisoblanadi ===\n")


class Togriturtburchak:
    def __init__(self, a: float, b: float):
        self.a, self.b = a, b

    @property
    def yuza(self) -> float:
        return self.a * self.b

    @property
    def perimetr(self) -> float:
        return 2 * (self.a + self.b)

    @property
    def diagonal(self) -> float:
        return (self.a ** 2 + self.b ** 2) ** 0.5

    @property
    def kvadratmi(self) -> bool:
        return self.a == self.b

    def __repr__(self):
        return f"Togriturtburchak({self.a}, {self.b})"


t = Togriturtburchak(3, 4)
print(f"  {t}: yuza={t.yuza}, perimetr={t.perimetr}, "
      f"diagonal={t.diagonal:.2f}")

t.a = 10
print(f"  a=10 → yuza={t.yuza} ✅, perimetr={t.perimetr} ✅")

t.a = t.b = 5
print(f"  a=b=5 → kvadratmi={t.kvadratmi} ✅")

print(f"\n  ⭐ Chaqiruvchi kod o'zgarmaydi:")
print(f"     t.yuza      ← metod emas, atribut kabi")
print(f"     t.yuza()    ❌ TypeError")


print("\n=== 3. @cached_property — bir marta hisoblanadi ===\n")


class Hujjat:
    def __init__(self, matn: str):
        self.matn = matn

    @property
    def sozlar_sekin(self) -> list[str]:
        """Har safar qayta hisoblanadi."""
        time.sleep(0.02)                    # og'ir ishni taqlid qilamiz
        return self.matn.split()

    @cached_property
    def sozlar(self) -> list[str]:
        """⭐ Bir marta, keyin keshdan."""
        time.sleep(0.02)
        return self.matn.split()

    @cached_property
    def statistika(self) -> dict:
        return {
            "sozlar": len(self.sozlar),
            "noyob": len(set(self.sozlar)),
            "belgilar": len(self.matn),
        }


h = Hujjat("Python juda kuchli va oddiy dasturlash tili " * 200)

print(f"  @property (har safar hisoblanadi):")
boshlanish = time.perf_counter()
for _ in range(5):
    _ = h.sozlar_sekin
print(f"    5 marta: {time.perf_counter() - boshlanish:.3f}s")

print(f"\n  @cached_property (bir marta):")
boshlanish = time.perf_counter()
for _ in range(5):
    _ = h.sozlar
print(f"    5 marta: {time.perf_counter() - boshlanish:.3f}s")

print(f"\n  Kesh __dict__ ga yoziladi:")
print(f"    'sozlar' in h.__dict__ = {'sozlar' in h.__dict__}")
print(f"    h.statistika = {h.statistika}")
print(f"    __dict__ kalitlari: {sorted(h.__dict__)}")

print(f"\n  ⚠️ TUZOQ — asos o'zgarsa kesh eskiradi:")
h.matn = "Butunlay boshqa matn"
print(f"    h.matn o'zgardi, lekin:")
print(f"    h.sozlar[:3] = {h.sozlar[:3]}   ⚠️ eski qiymat!")

del h.__dict__["sozlar"]
del h.__dict__["statistika"]
print(f"\n    del h.__dict__['sozlar'] dan keyin:")
print(f"    h.sozlar = {h.sozlar}   ✅")


print("\n=== 4. ✅ Keshni to'g'ri boshqarish ===\n")


class HujjatYaxshi:
    """Asos o'zgarganda kesh avtomatik tozalanadi."""

    def __init__(self, matn: str):
        self._matn = matn
        self._kesh: dict[str, object] = {}

    @property
    def matn(self) -> str:
        return self._matn

    @matn.setter
    def matn(self, qiymat: str) -> None:
        self._matn = qiymat
        self._kesh.clear()                  # ⭐ kesh bekor qilinadi
        print(f"      [kesh tozalandi]")

    @property
    def sozlar(self) -> list[str]:
        if "sozlar" not in self._kesh:
            print(f"      [hisoblanmoqda: sozlar]")
            self._kesh["sozlar"] = self._matn.split()
        return self._kesh["sozlar"]

    @property
    def noyob(self) -> set[str]:
        if "noyob" not in self._kesh:
            print(f"      [hisoblanmoqda: noyob]")
            self._kesh["noyob"] = set(s.lower() for s in self.sozlar)
        return self._kesh["noyob"]


hy = HujjatYaxshi("bir ikki uch bir ikki")

print(f"  hy.sozlar →")
print(f"    {hy.sozlar}")
print(f"  hy.sozlar (2-marta) →")
print(f"    {hy.sozlar}   ← keshdan, hisoblash yo'q")
print(f"  hy.noyob →")
print(f"    {sorted(hy.noyob)}")

print(f"\n  hy.matn = 'yangi matn' →")
hy.matn = "yangi matn keldi"
print(f"  hy.sozlar →")
print(f"    {hy.sozlar}   ✅ qayta hisoblandi")


print("\n=== 5. Qachon nima ===\n")

print("""
  ┌────────────────────┬──────────────────────────────────────┐
  │ Vosita             │ Qachon                               │
  ├────────────────────┼──────────────────────────────────────┤
  │ Oddiy atribut      │ Mustaqil ma'lumot (a, b)             │
  │ @property          │ Arzon hisoblash, asos o'zgaruvchan   │
  │ @cached_property   │ Qimmat hisoblash, asos O'ZGARMAS     │
  │ Qo'lda kesh        │ Qimmat + asos o'zgaradi              │
  │ Metod ()           │ Argument oladi yoki yon ta'siri bor  │
  └────────────────────┴──────────────────────────────────────┘

  ⭐ MEZON — property yoki metod?

     Atribut kabi tuyulsa (ot):        @property
       t.yuza, h.sozlar, u.toliq_ism

     Amal kabi tuyulsa (fe'l):         metod
       h.saqla(), t.aylantir(90)

     Argument kerak bo'lsa:            metod
       h.qidir("naqsh")

     Qimmat va kutilmagan bo'lsa:      metod
       db.hamma_qatorlar()   ← property bo'lsa aldaydi
""")


print("=== 6. ⚠️ property tuzoqlari ===\n")

print("""  ⚠️ 1. Yon ta'sirli property:

     @property
     def malumot(self):
         return tarmoqdan_yukla()       # ⚠️ obj.malumot sekin!

     ✅ def malumot_yukla(self): ...    metod qiling


  ⚠️ 2. Istisno tashlaydigan property:

     @property
     def x(self):
         return self._d["x"]            # ⚠️ KeyError kutilmagan

     ✅ return self._d.get("x")


  ⚠️ 3. Rekursiya:

     @property
     def x(self):
         return self.x                  # ❌ cheksiz rekursiya
         return self._x                 # ✅


  ⚠️ 4. Setter da o'zini chaqirish:

     @x.setter
     def x(self, q):
         self.x = q                     # ❌ cheksiz rekursiya
         self._x = q                    # ✅
""")

Natijaning muhim qismi:

text
=== ⚠️ 1. Saqlangan hosila qiymat eskiradi ===

  Boshida:  a=3, b=4, yuza=12, perimetr=14
  a=10:     a=10, b=4, yuza=12 ⚠️, perimetr=14 ⚠️
  Kutilgan: yuza=40, perimetr=28

=== 3. @cached_property — bir marta hisoblanadi ===

  @property (har safar hisoblanadi):
    5 marta: 0.103s

  @cached_property (bir marta):
    5 marta: 0.021s

  ⚠️ TUZOQ — asos o'zgarsa kesh eskiradi:
    h.sozlar[:3] = ['Python', 'juda', 'kuchli']   ⚠️ eski qiymat!

=== 4. ✅ Keshni to'g'ri boshqarish ===

  hy.sozlar →
      [hisoblanmoqda: sozlar]
    ['bir', 'ikki', 'uch', 'bir', 'ikki']
  hy.sozlar (2-marta) →
    ['bir', 'ikki', 'uch', 'bir', 'ikki']   ← keshdan

Nima ko'rsatdi: 2.5-bo'lim.

Misol 4 — Amaliy: konfiguratsiya sinfi

python
"""To'liq __init__ loyihasi — barcha tamoyillar birga."""

from __future__ import annotations

import json
import os
from dataclasses import dataclass, field, asdict
from pathlib import Path
from typing import Any, Literal

print("=== SERVER KONFIGURATSIYASI ===\n")

Muhit = Literal["ishlab_chiqish", "sinov", "ishlab_chiqarish"]


class Sozlama:
    """Server konfiguratsiyasi.

    INVARIANTLAR:
      1. 1 <= port <= 65535
      2. muhit — uch qiymatdan biri
      3. ishlab_chiqarish da debug = False
      4. maks_ulanish >= 1
      5. sirlar repr da ko'rinmaydi
    """

    MUHITLAR: frozenset[str] = frozenset(
        {"ishlab_chiqish", "sinov", "ishlab_chiqarish"}
    )
    SUKUT_PORT = 8000
    PORT_ORALIGI = (1, 65535)

    def __init__(
        self,
        host: str = "localhost",
        port: int = SUKUT_PORT,
        muhit: Muhit = "ishlab_chiqish",
        *,
        debug: bool | None = None,
        maks_ulanish: int = 100,
        ruxsat_etilgan: list[str] | None = None,
        sirlar: dict[str, str] | None = None,
    ) -> None:
        # ── 1. Tekshirish ──
        if not isinstance(port, int) or isinstance(port, bool):
            raise TypeError(f"Port butun son bo'lsin: {type(port).__name__}")
        past, yuqori = self.PORT_ORALIGI
        if not past <= port <= yuqori:
            raise ValueError(f"Port {past}–{yuqori} oralig'ida: {port}")
        if muhit not in self.MUHITLAR:
            raise ValueError(
                f"Muhit {sorted(self.MUHITLAR)} dan biri bo'lsin: {muhit!r}"
            )
        if maks_ulanish < 1:
            raise ValueError(f"maks_ulanish >= 1 bo'lsin: {maks_ulanish}")
        if not host.strip():
            raise ValueError("Host bo'sh bo'lmasin")

        # ── 2. Normallashtirish ──
        self.host = host.strip().lower()
        self.port = port
        self.muhit: Muhit = muhit
        self.maks_ulanish = maks_ulanish

        # ── 3. Bog'liq qiymatlar (invariant 3) ──
        if debug is None:
            debug = muhit != "ishlab_chiqarish"
        if debug and muhit == "ishlab_chiqarish":
            raise ValueError("Ishlab chiqarishda debug yoqilmasin")
        self.debug = debug

        # ── 4. Nusxa olish ──
        self.ruxsat_etilgan = list(ruxsat_etilgan or ["*"])

        # ── 5. Ichki holat (hammasi shu yerda) ──
        self._sirlar = dict(sirlar or {})
        self._muzlatilgan = False
        self._ozgartirishlar: list[str] = []

    # ── Fabrika metodlari ──

    @classmethod
    def muhitdan(cls, prefiks: str = "APP_") -> Sozlama:
        """Muhit o'zgaruvchilaridan yaratadi."""
        def olish(nom: str, sukut: Any = None) -> Any:
            return os.environ.get(f"{prefiks}{nom.upper()}", sukut)

        port = olish("PORT")
        return cls(
            host=olish("HOST", "localhost"),
            port=int(port) if port else cls.SUKUT_PORT,
            muhit=olish("ENV", "ishlab_chiqish"),
            debug=olish("DEBUG", "").lower() in ("1", "true", "ha"),
            maks_ulanish=int(olish("MAX_CONN", 100)),
        )

    @classmethod
    def jsondan(cls, matn: str) -> Sozlama:
        """JSON satridan yaratadi."""
        try:
            m = json.loads(matn)
        except json.JSONDecodeError as e:
            raise ValueError(f"Noto'g'ri JSON: {e}") from e
        if not isinstance(m, dict):
            raise ValueError("JSON obyekt bo'lsin")
        return cls(**m)

    @classmethod
    def sukut(cls, muhit: Muhit = "ishlab_chiqish") -> Sozlama:
        """Muhitga mos tayyor sozlama."""
        tayyor = {
            "ishlab_chiqish":     dict(host="localhost", port=8000,
                                       maks_ulanish=10),
            "sinov":              dict(host="0.0.0.0", port=8001,
                                       maks_ulanish=50),
            "ishlab_chiqarish":   dict(host="0.0.0.0", port=80,
                                       maks_ulanish=1000, debug=False),
        }
        return cls(muhit=muhit, **tayyor[muhit])

    # ── Hosila qiymatlar ──

    @property
    def url(self) -> str:
        sxema = "https" if self.muhit == "ishlab_chiqarish" else "http"
        port = "" if self.port in (80, 443) else f":{self.port}"
        return f"{sxema}://{self.host}{port}"

    @property
    def ishlab_chiqarishmi(self) -> bool:
        return self.muhit == "ishlab_chiqarish"

    @property
    def muzlatilgan(self) -> bool:
        return self._muzlatilgan

    # ── Amallar ──

    def sir(self, nom: str) -> str:
        """Sirni oladi (jurnal yozilmaydi)."""
        if nom not in self._sirlar:
            raise KeyError(f"Sir topilmadi: {nom!r}")
        return self._sirlar[nom]

    def sir_orn(self, nom: str, qiymat: str) -> None:
        if self._muzlatilgan:
            raise RuntimeError("Sozlama muzlatilgan")
        self._sirlar[nom] = qiymat
        self._ozgartirishlar.append(f"sir:{nom}")

    def muzlat(self) -> None:
        """Boshqa o'zgartirib bo'lmaydi."""
        self._muzlatilgan = True

    def __setattr__(self, nom: str, qiymat: Any) -> None:
        """⭐ Muzlatilgandan keyin yozishni to'sadi."""
        if getattr(self, "_muzlatilgan", False) and not nom.startswith("_"):
            raise RuntimeError(f"Sozlama muzlatilgan: {nom}")
        super().__setattr__(nom, qiymat)

    def lugat(self, sirlar_bilan: bool = False) -> dict[str, Any]:
        m = {
            "host": self.host, "port": self.port, "muhit": self.muhit,
            "debug": self.debug, "maks_ulanish": self.maks_ulanish,
            "ruxsat_etilgan": list(self.ruxsat_etilgan),
        }
        if sirlar_bilan:
            m["sirlar"] = dict(self._sirlar)
        return m

    def __repr__(self) -> str:
        """⭐ Sirlar KO'RINMAYDI."""
        muz = " MUZLATILGAN" if self._muzlatilgan else ""
        return (f"Sozlama({self.host!r}, port={self.port}, "
                f"muhit={self.muhit!r}, debug={self.debug}, "
                f"sirlar={len(self._sirlar)} ta{muz})")


print("1. Uch xil yaratish usuli:\n")

s1 = Sozlama()
s2 = Sozlama.sukut("ishlab_chiqarish")
s3 = Sozlama.jsondan('{"host": "api.wisar.uz", "port": 3000, '
                     '"muhit": "sinov"}')

os.environ.update({"APP_HOST": "test.local", "APP_PORT": "9000"})
s4 = Sozlama.muhitdan()

for nom, s in [("Sozlama()", s1), ("sukut('ishlab_chiqarish')", s2),
               ("jsondan(...)", s3), ("muhitdan()", s4)]:
    print(f"  {nom:<26} {s}")
    print(f"  {'':<26} url = {s.url}")


print("\n\n2. Invariant himoyasi:\n")

XATOLAR = [
    ("port=0",              dict(port=0)),
    ("port=70000",          dict(port=70000)),
    ("port='8000'",         dict(port="8000")),
    ("port=True",           dict(port=True)),
    ("muhit='prod'",        dict(muhit="prod")),
    ("maks_ulanish=0",      dict(maks_ulanish=0)),
    ("host=''",             dict(host="")),
    ("ishlab_chiqarish + debug", dict(muhit="ishlab_chiqarish", debug=True)),
]

for tavsif, kwargs in XATOLAR:
    try:
        Sozlama(**kwargs)
        natija = "⚠️ O'TDI"
    except (ValueError, TypeError) as e:
        natija = f"✅ {type(e).__name__}: {str(e)[:46]}"
    print(f"  {tavsif:<28} {natija}")


print("\n\n3. ⭐ Sirlar himoyasi:\n")

s = Sozlama(muhit="sinov")
s.sir_orn("db_parol", "juda-maxfiy-parol-123")
s.sir_orn("api_kalit", "sk-abc123xyz")

print(f"  repr(s):     {s!r}")
print(f"    ✅ sirlar QIYMATI ko'rinmaydi\n")
print(f"  s.lugat():   {s.lugat()}")
print(f"    ✅ sirlar yo'q\n")
print(f"  s.sir('db_parol') = {s.sir('db_parol')!r}   ← aniq so'ralganda")

try:
    s.sir("yoq")
except KeyError as e:
    print(f"  s.sir('yoq') → ✅ KeyError: {e}")


print("\n\n4. Muzlatish:\n")

print(f"  Muzlatishdan oldin:")
s.port = 9999
print(f"    s.port = 9999  ✅")

s.muzlat()
print(f"\n  s.muzlat() dan keyin:")

for kod, f in [
    ("s.port = 8888",           lambda: setattr(s, "port", 8888)),
    ("s.host = 'x'",            lambda: setattr(s, "host", "x")),
    ("s.sir_orn('a', 'b')",     lambda: s.sir_orn("a", "b")),
    ("s.yangi_atribut = 1",     lambda: setattr(s, "yangi_atribut", 1)),
]:
    try:
        f(); n = "⚠️ O'TDI"
    except RuntimeError as e:
        n = f"✅ RuntimeError: {e}"
    print(f"    {kod:<24} {n}")

print(f"\n  Yakuniy: {s!r}")


print("\n\n5. Hosila qiymatlar:\n")

for muhit, port in [("ishlab_chiqish", 8000), ("sinov", 8080),
                    ("ishlab_chiqarish", 443), ("ishlab_chiqarish", 80)]:
    try:
        k = Sozlama(host="wisar.uz", port=port, muhit=muhit)
        print(f"  {muhit:<20} port={port:<6} → {k.url}")
    except ValueError as e:
        print(f"  {muhit:<20} port={port:<6} → ❌ {e}")


print("\n\n6. dataclass versiyasi (solishtirish):\n")


@dataclass
class SozlamaD:
    """Xuddi shu narsa — kamroq kod."""
    host: str = "localhost"
    port: int = 8000
    muhit: str = "ishlab_chiqish"
    debug: bool = True
    maks_ulanish: int = 100
    ruxsat_etilgan: list[str] = field(default_factory=lambda: ["*"])
    _sirlar: dict[str, str] = field(default_factory=dict, repr=False)

    def __post_init__(self) -> None:
        if not 1 <= self.port <= 65535:
            raise ValueError(f"Port 1–65535: {self.port}")
        if self.muhit not in Sozlama.MUHITLAR:
            raise ValueError(f"Noma'lum muhit: {self.muhit!r}")
        if self.debug and self.muhit == "ishlab_chiqarish":
            raise ValueError("Ishlab chiqarishda debug yoqilmasin")
        self.host = self.host.strip().lower()

    @property
    def url(self) -> str:
        return f"http://{self.host}:{self.port}"


d = SozlamaD(host="  API.WISAR.UZ  ", port=3000)
print(f"  {d}")
print(f"  url: {d.url}")
print(f"  asdict: {asdict(d)}")

try:
    SozlamaD(port=99999)
except ValueError as e:
    print(f"\n  Tekshiruv: ✅ ValueError: {e}")

print(f"""

  ┌────────────────────┬──────────────┬──────────────┐
  │ Xususiyat          │ Qo'lda       │ dataclass    │
  ├────────────────────┼──────────────┼──────────────┤
  │ Kod qatorlari      │ ~70          │ ~20          │
  │ __init__           │ qo'lda       │ avtomatik    │
  │ __repr__           │ qo'lda       │ avtomatik    │
  │ __eq__             │ qo'lda       │ avtomatik    │
  │ Sukut tuzog'i      │ ehtiyot      │ TAQIQLANGAN  │
  │ Tekshiruv          │ __init__     │ __post_init__│
  │ Nozik nazorat      │ ✅ to'liq    │ ⚠️ cheklangan│
  │ __setattr__ nazorat│ ✅           │ ⚠️ frozen    │
  └────────────────────┴──────────────┴──────────────┘

  ⭐ TANLASH:
     Asosan ma'lumot                 → dataclass
     Murakkab tekshiruv va nazorat   → qo'lda __init__
     Ikkalasi ham                    → dataclass + __post_init__
""")

Natijaning muhim qismi:

text
1. Uch xil yaratish usuli:

  Sozlama()                  Sozlama('localhost', port=8000, muhit=...)
                             url = http://localhost:8000
  sukut('ishlab_chiqarish')  Sozlama('0.0.0.0', port=80, ...)
                             url = https://0.0.0.0

2. Invariant himoyasi:

  port=0                       ✅ ValueError: Port 1–65535 oralig'ida: 0
  port='8000'                  ✅ TypeError: Port butun son bo'lsin: str
  port=True                    ✅ TypeError: Port butun son bo'lsin: bool
  ishlab_chiqarish + debug     ✅ ValueError: Ishlab chiqarishda debug...

3. ⭐ Sirlar himoyasi:

  repr(s):     Sozlama('localhost', port=8000, muhit='sinov',
               debug=True, sirlar=2 ta)
    ✅ sirlar QIYMATI ko'rinmaydi

4. Muzlatish:

    s.port = 8888            ✅ RuntimeError: Sozlama muzlatilgan: port
    s.sir_orn('a', 'b')      ✅ RuntimeError: Sozlama muzlatilgan

Nima ko'rsatdi: 2.1, 2.4, 2.5, 2.6, 2.7-bo'limlar.


5. To'g'ri va noto'g'ri tushunishlar

Noto'g'ri fikr To'g'risi
"__init__ obyektni yaratadi" __new__ yaratadi, __init__ sozlaydi
"def __init__(self, lst=[]) xavfsiz" Sukut bir marta yaratiladi
"x or [] — x is None bilan bir xil" 0, "", [] ni ham almashtiradi
"Atributni istalgan metodda yaratsa bo'ladi" Texnik ha, lekin __init__ da e'lon qiling
"Hisoblangan qiymatni saqlash tez" Eskiradi. @property yoki kesh boshqaruvi
"__init__ bir nechta bo'lishi mumkin" Oxirgisi qoladi. @classmethod ishlating
"self.x = x doim yetarli" Konteyner uchun nusxa oling
"dataclass — faqat sodda holatlar" __post_init__ bilan murakkab ham

6. Keng tarqalgan xatolar va yechimlari

1. O'zgaruvchan sukut

python
def __init__(self, lst=[]):         # ❌
def __init__(self, lst=None):       # ✅
    self.lst = list(lst or [])

2. or bilan falsy qiymatni yo'qotish

python
self.n = n or 100                   # ⚠️ n=0 bo'lsa 100
self.n = 100 if n is None else n    # ✅

3. Nusxa olmaslik

python
self.lst = lst                      # ⚠️ tashqi havola
self.lst = list(lst)                # ✅

4. Atributni boshqa metodda yaratish

python
def hisobla(self):
    self.natija = ...               # ⚠️ __init__ da yo'q

def __init__(self):
    self.natija = None              # ✅ e'lon

5. Hosila qiymatni saqlash

python
self.yuza = a * b                   # ⚠️ eskiradi
@property
def yuza(self): return self.a * self.b  # ✅

6. __init__ da og'ir ish

python
def __init__(self, url):
    self.d = yukla(url)             # ⚠️ sekin, xato beradi

@classmethod
def urldan(cls, url):               # ✅
    return cls(yukla(url))

7. super().__init__() unutish

python
class B(A):
    def __init__(self, a, b):
        self.b = b                  # ⚠️ A.__init__ chaqirilmadi
        super().__init__(a)         # ✅

8. dataclass da o'zgaruvchan sukut

python
lst: list = []                              # ❌ ValueError
lst: list = field(default_factory=list)     # ✅

7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 7.3-dars (o'tilgan): sukut argument tuzog'i — bu yerda takrorlandi
  • 8.5-dars: sinf va nusxa atributlari farqi
  • 8.7-dars: super().__init__() va meros
  • 8.15-dars: @property to'liq
  • 8.16-dars: @classmethod fabrika metodlari
  • 8.17-dars: dataclass va __post_init__ to'liq
  • 13-qism: pydantic — tekshiruv avtomatik

8. Eng yaxshi amaliyotlar

  1. Sukut — None, o'zgaruvchan obyekt emas. Har konteyner uchun.

  2. is None bilan tekshiring, or bilan emas. 0, "", [] yo'qolmasin.

  3. Konteynerdan nusxa oling. list(lst) — tashqi o'zgarishdan himoya.

  4. Barcha atributlarni __init__ da e'lon qiling. Boshqa metodda tug'ilmasin.

  5. Invariantni __init__ da tekshiring. Noto'g'ri obyekt yaratilmasin.

  6. Hosila qiymatni @property qiling. Saqlangan qiymat eskiradi.

  7. __init__ tez va ishonchli bo'lsin. Og'ir ish — fabrika metodida.

  8. Ko'p yaratish usuli kerak bo'lsa — @classmethod. isinstance shoxlanishi emas.


9. Amaliy topshiriq

Vazifa 1: Natijani bashorat qiling

python
1.  class A:
        def __init__(self, l=[]): self.l = l
    a, b = A(), A()
    a.l.append(1)
    print(b.l)
2.  class A:
        def __init__(self, l=None): self.l = l or []
    a, b = A(), A()
    a.l.append(1)
    print(b.l)
3.  class A:
        def __init__(self, n=None): self.n = n or 10
    print(A(0).n)
4.  class A:
        def __init__(self, l): self.l = l
    x = [1]
    a = A(x)
    x.append(2)
    print(a.l)
5.  class A:
        def __init__(self, a, b):
            self.a, self.b = a, b
            self.s = a + b
    x = A(1, 2)
    x.a = 10
    print(x.s)
6.  class A:
        def __init__(self): print("A")
        def __init__(self): print("B")
    A()
7.  class A:
        def __init__(self): return None
    print(type(A()))
8.  from dataclasses import dataclass
    @dataclass
    class A:
        x: int = 0
    print(A() == A())
9.  class A:
        def __init__(self, x): self.x = x
        @property
        def y(self): return self.x * 2
    a = A(3)
    a.x = 5
    print(a.y)
10. class A:
        def __init__(self, x): self.x = x
    print(A(1).__dict__)
11. class A:
        def __init__(self, **kw):
            self.__dict__.update(kw)
    print(A(a=1, b=2).__dict__)
12. class A:
        def __init__(self, t=()): self.t = t
    a, b = A(), A()
    print(a.t is b.t)
Javoblar
  1. [1] — umumiy sukut ro'yxat
  2. [] — har safar yangi
  3. 10 — 0 or 10 → 10
  4. [1, 2] — nusxa olinmadi
  5. 3 — s eskirdi
  6. B — ikkinchi __init__ birinchisini almashtiradi
  7. <class '__main__.A'> — return None ruxsat
  8. True — dataclass __eq__ beradi
  9. 10 — property har safar hisoblaydi
  10. {'x': 1}
  11. {'a': 1, 'b': 2}
  12. True — tuple o'zgarmas, xavfsiz

Vazifa 2: Xatolarni tuzating

python
1.  def __init__(self, d={}): self.d = d
2.  def __init__(self, n=None): self.n = n or 100
3.  def __init__(self, lst): self.lst = lst
4.  def __init__(self, a, b):
        self.a, self.b = a, b
        self.yuza = a * b
5.  def __init__(self, url): self.d = requests.get(url).json()
6.  class B(A):
        def __init__(self, x, y):
            self.y = y
7.  @dataclass
    class A:
        teglar: set = set()
8.  def __init__(self, x):
        self.x = x
    def hisobla(self):
        self.natija = self.x * 2
Javoblar
python
1.  def __init__(self, d=None): self.d = dict(d or {})
2.  self.n = 100 if n is None else n
3.  self.lst = list(lst)
4.  @property
    def yuza(self): return self.a * self.b
5.  @classmethod
    def urldan(cls, url): return cls(requests.get(url).json())
6.  super().__init__(x)  qo'shing
7.  teglar: set = field(default_factory=set)
8.  __init__ da:  self.natija: int | None = None

Vazifa 3: Vaqt sinfi

Yozing:

  1. __init__(self, soat, daqiqa, soniya=0) — to'liq tekshiruv
  2. Invariant: 0<=soat<24, 0<=daqiqa<60, 0<=soniya<60
  3. @property jami_soniya
  4. @classmethod soniyadan(cls, n)
  5. @classmethod satrdan(cls, "14:30:00")
  6. @classmethod hozir(cls)
  7. __repr__, __str__

Vazifa 4: Sukut tuzoq detektori

Vosita yozing:

  1. Modul sinflarini skanerlasin
  2. __init__ da o'zgaruvchan sukut argumentlarni topsin
  3. O'zgaruvchan sinf atributlarini topsin
  4. x or [] naqshini topsin (falsy tuzog'i)
  5. Nusxa olinmagan konteyner argumentlarini
  6. Har biri uchun tuzatish taklifi

Vazifa 5: Matritsa sinfi

  1. __init__(self, qatorlar) — to'g'ri o'lchamni tekshirsin
  2. Ichki ro'yxatlardan chuqur nusxa olsin
  3. @property olcham, transponirlangan
  4. @classmethod nol(cls, n, m), birlik(cls, n)
  5. Ichki ma'lumot tashqaridan o'zgarmasin
  6. Har amal yangi obyekt qaytarsin (o'zgarmas)

Vazifa 6: Kesh boshqaruvi

  1. Hujjat sinfi — matn xususiyati
  2. sozlar, jumlalar, statistika — keshlanadigan
  3. matn o'zgarganda kesh avtomatik tozalansin
  4. @cached_property va qo'lda keshni solishtiring
  5. Kesh urish/o'tkazib yuborish statistikasi
  6. Xotira hajmini o'lchang

Vazifa 7: O'ylash

Nega Python __init__ da self.x = x yozishni majburiy qiladi — nega konstruktor argumentlari avtomatik atributga aylanmaydi (TypeScript ning constructor(private x: number) kabi)?

Javob

Chunki avtomatik bog'lash aniqlikni yo'qotadi, va Pythonning javobi boshqa — dataclass.

1. Avtomatik bog'lash nima demak

Ba'zi tillarda konstruktor argumenti to'g'ridan-to'g'ri maydonga aylanadi:

typescript
// TypeScript
class Nuqta {
    constructor(private x: number, public y: number) {}
    // x va y avtomatik this.x, this.y bo'ladi
}
kotlin
// Kotlin
class Nuqta(val x: Double, val y: Double)
scala
// Scala
class Nuqta(val x: Double, val y: Double)

Pythonda esa:

python
class Nuqta:
    def __init__(self, x, y):
        self.x = x              # majburiy
        self.y = y

2. Nega Python bunday qilmagan

A) __init__ — oddiy metod, maxsus sintaksis yo'q

python
class A:
    def __init__(self, x): ...

__init__ — bu shunchaki maxsus nomli metod. Uning imzosi boshqa metodlardan farq qilmaydi. Avtomatik bog'lash uchun Python __init__ ni maxsus tarzda parse qilishi kerak bo'lardi — bu Pythonning "hamma narsa bir xil qoidaga bo'ysunadi" tamoyilini buzadi.

B) Argument ≠ atribut

Ko'p holatda ular mos kelmaydi:

python
class Doira:
    def __init__(self, diametr):        # argument
        self.radius = diametr / 2       # atribut — boshqa nom, boshqa qiymat

class Foydalanuvchi:
    def __init__(self, ism, familiya):
        self.toliq_ism = f"{ism} {familiya}"    # ikki argument — bir atribut

class Hisob:
    def __init__(self, boshlangich_balans):
        self._balans = boshlangich_balans       # nom o'zgardi (_ prefiks)
        self._tarix = []                        # argumentsiz atribut

class Ulanish:
    def __init__(self, url, foydalanuvchi, parol):
        self.url = url
        self._soket = ulan(url, foydalanuvchi, parol)   # argumentlar
                                        # ishlatildi, lekin saqlanmadi

Agar bog'lash avtomatik bo'lsa, bu holatlar uchun istisno sintaksisi kerak bo'lardi.

C) Tekshiruv va normallashtirish qayerda?

python
def __init__(self, ism, yosh):
    if yosh < 0:
        raise ValueError(...)
    self.ism = ism.strip().title()      # normallashtirish
    self.yosh = yosh

Avtomatik bog'lash bilan bu bog'lashdan keyin bo'lishi kerak — ya'ni obyekt bir lahza noto'g'ri holatda bo'ladi. TypeScript da aynan shunday muammo bor.

D) "Explicit is better than implicit"

self.x = x yozilgan qatorni ko'rgan o'quvchi aniq biladi: bu atribut bor, ommaviy, argumentdan keladi. Avtomatik bo'lsa — imzoga qarab taxmin qilish kerak.

3. Pythonning javobi — dataclass

Python 3.7 (2018) da muammo boshqacha hal qilindi:

python
from dataclasses import dataclass

@dataclass
class Nuqta:
    x: float
    y: float = 0.0

Bu — kod generatsiyasi, sintaksis o'zgarishi emas:

python
# @dataclass avtomatik yozadi:
def __init__(self, x: float, y: float = 0.0) -> None:
    self.x = x
    self.y = y

def __repr__(self): ...
def __eq__(self, other): ...

Nega bu yaxshiroq:

TypeScript private x Python @dataclass
Sintaksis o'zgarishi Til darajasida Oddiy dekorator
Faqat __init__ __repr__, __eq__ ham
Tekshiruv Qo'shimcha kod __post_init__
Sukut tuzog'i Mumkin Taqiqlangan
O'chirib qo'yish Oddiy __init__ yozing
Kutubxonalar attrs, pydantic, msgspec

4. dataclass nima beradi

python
@dataclass
class Foydalanuvchi:
    ism: str
    email: str
    yosh: int = 0
    teglar: list[str] = field(default_factory=list)     # ⭐ tuzoq yo'q
    parol: str = field(repr=False, default="")          # ⭐ sirlar yashirin

    def __post_init__(self):
        if self.yosh < 0:
            raise ValueError("Yosh manfiy bo'lmasin")
        self.email = self.email.lower()

Bepul:

python
u = Foydalanuvchi("Aziz", "A@B.uz", 30)
u                                   # Foydalanuvchi(ism='Aziz', ...)
u == Foydalanuvchi("Aziz", "a@b.uz", 30)    # True
asdict(u)                           # lug'atga
replace(u, yosh=31)                 # yangi nusxa

Qo'shimcha rejimlar:

python
@dataclass(frozen=True)             # o'zgarmas + hashable
@dataclass(slots=True)              # __slots__ (3.10+), xotira tejaydi
@dataclass(order=True)              # <, >, <=, >=
@dataclass(kw_only=True)            # faqat nomli argumentlar (3.10+)

5. Ekotizim

dataclass — standart, lekin muqobillar ham bor:

Kutubxona Xususiyat
dataclasses Standart, bog'liqliksiz
attrs dataclass ning ilhomlantiruvchisi, ko'proq imkoniyat
pydantic Ish vaqtida tekshiruv, JSON, FastAPI
msgspec Eng tez serializatsiya
NamedTuple Yengil, o'zgarmas, tuple kabi
python
# pydantic — tur avtomatik tekshiriladi va o'giriladi
from pydantic import BaseModel, EmailStr, Field

class Foydalanuvchi(BaseModel):
    ism: str = Field(min_length=1, max_length=100)
    email: EmailStr
    yosh: int = Field(ge=0, le=150)

Foydalanuvchi(ism="Aziz", email="a@b.uz", yosh="30")   # "30" → 30
Foydalanuvchi(ism="", email="xato", yosh=-1)           # ValidationError

6. Qachon qo'lda __init__

dataclass hamma narsani hal qilmaydi:

python
# ⚠️ dataclass mos kelmaydi:

class Ulanish:
    def __init__(self, url, foydalanuvchi, parol):
        self._soket = ulan(url, foydalanuvchi, parol)   # argument saqlanmaydi
        self._urinish = 0
        self.url = url

class Doira:
    def __init__(self, diametr):
        self.radius = diametr / 2                        # nom o'zgaradi

class Kesh:
    def __init__(self, hajm=100):
        self._m = OrderedDict()
        self._hajm = hajm
        self._urish = self._otkazish = 0                 # hosila holat

Mezon: argumentlar atributlarga bir-bir mos kelsa — dataclass. Aks holda — qo'lda.

7. Tarixiy izoh

Guido dataclass dan oldin ham taklif qabul qilmagan edi. PEP 557 (2017) muallifi Eric V. Smith yozadi:

"Bu — attrs dan ilhomlangan, lekin soddaroq. Maqsad — namedtuple va to'liq sinf orasidagi bo'shliqni to'ldirish."

Ya'ni yechim tilni o'zgartirmasdan, kutubxona darajasida topildi. Bu — Pythonning odatiy yo'li:

Muammo Yechim Til o'zgardimi
Ko'p self.x = x @dataclass dekorator
Xotira (ko'p nusxa) __slots__ sinf atributi
Tur tekshiruvi mypy tashqi vosita
Async async/await sintaksis (kamdan-kam)

Xulosa:

self.x = x majburiy, chunki:

  1. __init__ — oddiy metod, maxsus qoidasiz
  2. Argument va atribut ko'pincha mos kelmaydi
  3. Tekshiruv va normallashtirish uchun joy kerak
  4. Aniqlik — Pythonning asosiy qadriyati

Lekin takrorlanishdan qutulish uchun @dataclass bor — u ko'proq narsa beradi (__repr__, __eq__, sukut tuzog'i himoyasi) va tilni o'zgartirmaydi.

Nimani mustahkamlaydi: 2.1, 2.2, 2.7-bo'limlar.


Xulosa

Bu darsda __init__ va atributlarni to'g'ri loyihalashni o'rgandik.

Eng muhim uch fikr:

  1. Sukut qiymat tuzog'i sinflarda ikki xil ko'rinishda. def __init__(self, lst=[]) va class A: lst = [] — ikkalasi ham barcha nusxalar uchun bitta obyekt yaratadi. Yechim: None sukut + __init__ ichida yaratish, va konteynerdan nusxa olish (list(lst)). @dataclass bu tuzoqni umuman taqiqlaydi.

  2. __init__ — invariantning birinchi qo'riqchisi. Noto'g'ri obyekt umuman yaratilmasin: tekshiring, normallashtiring, keyin o'rnating. Barcha nusxa atributlari shu yerda e'lon qilinsin — boshqa metodda tug'ilmasin.

  3. Hisoblanadigan qiymatni saqlamang. self.yuza = a * b — a o'zgarsa eskiradi. @property har safar hisoblaydi va doim to'g'ri; qimmat hisoblash uchun @cached_property yoki qo'lda boshqariladigan kesh, lekin unda asos o'zgarganda keshni bekor qilish shart.

Keyingi darsda metodlar va self ni chuqur ko'ramiz: bog'langan metodlar, self aslida qanday uzatiladi va metodlarni sinfdan tashqarida yozish.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
8.3-dars: init va atributlar — IlmHamroh