IlmHamroh
Python kursi/OOP19/20-dars42 daqiqa
Mundarija (21)

8.19-dars: __slots__

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


1. Kirish va motivatsiya

Bir million nuqta — ~122 MB:

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

nuqtalar = [Nuqta(i, i) for i in range(1_000_000)]      # ~122 MB

Bir qator qo'shdik — ~84 MB:

python
class Nuqta:
    __slots__ = ("x", "y")                              # ⭐

    def __init__(self, x, y):
        self.x, self.y = x, y

nuqtalar = [Nuqta(i, i) for i in range(1_000_000)]      # ~84 MB

~31% kam xotira (Python 3.14, tracemalloc bilan o'lchangan; bu raqamga int obyektlari va ro'yxatning o'zi ham kiradi).

Tezlik bo'yicha esa zamonaviy Pythonda deyarli farq yo'q — __slots__ ning asosiy foydasi xotira (2.6-bo'lim).

Lekin narxi bor:

python
n = Nuqta(1, 2)
n.z = 3                         # ❌ AttributeError
n.__dict__                      # ❌ AttributeError
n.__weakref__                   # ❌ (agar qo'shilmasa)

@cached_property                # ❌ ishlamaydi
def masofa(self): ...

Bu darsda:

  • __slots__ nima qiladi va qanday tejaydi
  • Xotira va tezlik o'lchovlari
  • Beshta cheklov va ularni chetlab o'tish
  • Meros bilan murakkab qoidalar
  • __weakref__, __dict__ ni qaytarish
  • Qachon kerak emas

2. Nazariya — chuqur tushuntirish

2.1. Nima qiladi

__slots__ siz:

Nuqta obyekti (56 bayt)
  ├── ob_refcnt, ob_type       (16 bayt)
  ├── __dict__ ko'rsatkichi    (8 bayt)  →  dict (104+ bayt)
  └── __weakref__ ko'rsatkichi (8 bayt)      ├── 'x' → 1
                                             └── 'y' → 2

__slots__ bilan:

Nuqta obyekti (48 bayt)
  ├── ob_refcnt, ob_type       (16 bayt)
  ├── x qiymati ko'rsatkichi   (8 bayt)   ⭐ to'g'ridan-to'g'ri
  └── y qiymati ko'rsatkichi   (8 bayt)

Lug'at o'rniga — massiv. Har atribut aniq o'rin (offset) da saqlanadi.

Amalga oshirish:

python
class Nuqta:
    __slots__ = ("x", "y")

type(Nuqta.x)                   # <class 'member_descriptor'>
Nuqta.x.__get__, Nuqta.x.__set__  # ⭐ data deskriptor

Har slot uchun member_descriptor yaratiladi — u obyektning belgilangan baytidan o'qiydi.

2.2. Sintaksis

python
class A:
    __slots__ = ("x", "y")              # ✅ tuple (tavsiya)
    __slots__ = ["x", "y"]              # ✅ list
    __slots__ = "x"                     # ✅ bitta satr
    __slots__ = {"x": "X hujjati",      # ✅ dict — hujjat bilan
                 "y": "Y hujjati"}

dict shakli — help() da ko'rinadi.

Sukut qiymat berish mumkin emas:

python
class A:
    __slots__ = ("x",)
    x = 5                               # ❌ ValueError
ValueError: 'x' in __slots__ conflicts with class variable

Sabab: x = 5 sinf atributi member_descriptor ni to'sib qo'yardi.

2.3. Beshta cheklov

1. Yangi atribut qo'shib bo'lmaydi

python
n.z = 3                         # ❌ AttributeError

2. __dict__ yo'q

python
n.__dict__                      # ❌ AttributeError
vars(n)                         # ❌ TypeError

Buzilgan narsalar: pickle (eski protokol), copy, json, ba'zi kutubxonalar.

3. __weakref__ yo'q

python
weakref.ref(n)                  # ❌ TypeError

Qo'shish: __slots__ = ("x", "y", "__weakref__").

4. cached_property ishlamaydi

python
@cached_property                # ❌ TypeError — __dict__ kerak
def masofa(self): ...

5. Ko'p merosda cheklov

python
class A: __slots__ = ("x",)
class B: __slots__ = ("y",)
class C(A, B): ...              # ❌ TypeError
TypeError: multiple bases have instance lay-out conflict

2.4. Meros qoidalari

A) Avlodda __slots__ yo'q → __dict__ qaytadi:

python
class A:
    __slots__ = ("x",)

class B(A):
    pass                        # ⚠️ __slots__ yo'q

b = B()
b.z = 1                         # ✅ ishlaydi
b.__dict__                      # {'z': 1}  ⚠️ tejash yo'qoldi

Har avlodda __slots__ yozing — hatto bo'sh bo'lsa ham:

python
class B(A):
    __slots__ = ()              # ✅ tejash saqlanadi

B) Takrorlamang:

python
class A:
    __slots__ = ("x",)

class B(A):
    __slots__ = ("x", "y")      # ⚠️ 'x' ikki marta — xotira behuda
    __slots__ = ("y",)          # ✅ faqat yangisi

C) __dict__ bo'lgan ota-sinf:

python
class A:
    pass                        # __dict__ bor

class B(A):
    __slots__ = ("x",)          # ⚠️ foydasiz — __dict__ meros olinadi

D) Ko'p meros — faqat bittasida bo'sh bo'lmagan __slots__:

python
class A: __slots__ = ("x",)
class B: __slots__ = ()         # ⭐ bo'sh
class C(A, B): __slots__ = ()   # ✅ ishlaydi

2.5. Xotira o'lchovlari

Bitta obyekt egallaydigan xotira (Python 3.14, 64-bit, tracemalloc bilan o'lchangan):

Atribut soni __dict__ bilan __slots__ bilan Tejash
1 ~80 bayt ~40 bayt 50%
3 ~96 bayt ~56 bayt 42%
5 ~112 bayt ~72 bayt 36%
10 ~160 bayt ~112 bayt 30%
20 ~248 bayt ~192 bayt 23%

Python 3.11+ da farq ancha kamaydi. Endi oddiy sinf nusxasi ham atribut qiymatlarini alohida lug'atda emas, obyektning o'zida ("inline values") saqlaydi, kalitlar esa sinfning barcha nusxalari orasida ulashiladi. Haqiqiy lug'at faqat kerak bo'lganda yaratiladi.

O'lchash tuzog'i: sys.getsizeof(obj) + sys.getsizeof(obj.__dict__) usuli 3.11+ da noto'g'ri natija beradi — obj.__dict__ ga murojaat qilishning o'zi lug'atni yaratib qo'yadi:

python
# 200 000 ta ikki atributli obyekt, har biri uchun:
# __dict__ ga murojaatdan oldin:  ~88 bayt
# har biriga o.__dict__ dan keyin: ~152 bayt   ⚠️ o'lchov o'lchanayotganni o'zgartirdi

Ishonchli usul — ko'p obyekt yaratib, tracemalloc bilan umumiy o'sishni o'lchash.

Amaliy qoida: 100 000 dan kam obyekt bo'lsa — __slots__ kerak emas.

2.6. Tezlik

Python 3.14 da timeit bilan o'lchangan (bitta amal):

python
# Atribut o'qish
obj.x  (__dict__)               ~35 ns
obj.x  (__slots__)              ~34 ns          ≈ farq yo'q

# Atribut yozish
obj.x = 1  (__dict__)           ~37 ns
obj.x = 1  (__slots__)          ~40 ns          ⚠️ hatto biroz sekinroq

# Obyekt yaratish (2 atribut)
A(1, 2)  (__dict__)             ~210 ns
A(1, 2)  (__slots__)            ~193 ns         ~8% tezroq

Eski maqolalarda "__slots__ atributga kirishni 20% tezlashtiradi" degan gapni uchratasiz — bu eski Python versiyalariga tegishli. 3.11+ dagi ixtisoslashgan bayt-kod (PEP 659) ikkala holatni ham deyarli bir xil tezlikka keltirdi. __slots__ ni tezlik uchun emas, xotira uchun ishlating. Raqamlar mashina va versiyaga qarab farq qiladi — muhim qarorlardan oldin o'zingiz o'lchang.

2.7. Amaliy naqshlar

__dict__ ni qaytarish (aralash):

python
class A:
    __slots__ = ("x", "y", "__dict__")      # ⭐ ikkalasi ham

a = A()
a.x = 1                         # slot — tez
a.qoshimcha = 2                 # __dict__ — moslashuvchan

Bu tejashning katta qismini yo'qotadi.

dataclass bilan:

python
@dataclass(slots=True)                      # ⭐ 3.10+
class Nuqta:
    x: float
    y: float

3.10 gacha: __slots__ ni qo'lda yozish kerak, lekin sukut qiymatlar bilan konflikt bo'ladi.

NamedTuple — muqobil:

python
class Nuqta(NamedTuple):
    x: float
    y: float

Ko'pchilik "tuple asosida — demak eng kam xotira" deb o'ylaydi, lekin bu noto'g'ri. 1 000 000 ta ikki maydonli obyekt (Python 3.14):

__slots__ / dataclass(slots=True)   ~84 MB   ⭐ eng kam
tuple                               ~100 MB
NamedTuple                          ~107 MB
oddiy sinf                          ~122 MB

NamedTuple ni xotira uchun emas, o'zgarmaslik, indeks bo'yicha kirish va ochib olish (x, y = nuqta) kerak bo'lganda tanlang.

Pickle:

python
# ⭐ __slots__ bilan pickle ishlaydi (protokol 2+)
import pickle
pickle.dumps(obj, protocol=2)   # ✅

# ⚠️ Maxsus holat kerak bo'lsa:
def __getstate__(self):
    return {s: getattr(self, s) for s in self.__slots__}

def __setstate__(self, holat):
    for k, v in holat.items():
        setattr(self, k, v)

3. Tez ma'lumotnoma

Asosiy

python
class A:
    __slots__ = ("x", "y")      tuple (tavsiya)
    __slots__ = "x"             bitta satr
    __slots__ = {"x": "doc"}    hujjat bilan

⭐ __dict__ o'rniga MASSIV
   Har slot → member_descriptor (data deskriptor)

Cheklovlar

1. Yangi atribut          ❌ AttributeError
2. __dict__               ❌ (vars() ham)
3. __weakref__            ❌ → __slots__ ga qo'shing
4. cached_property        ❌ (__dict__ kerak)
5. Ko'p meros             ❌ ikkalasida ham bo'sh bo'lmagan slots
6. Sinf atributi bilan    ❌ ValueError (x = 5)

Meros

python
class B(A):
    __slots__ = ()          ⭐ SHART — aks holda __dict__ qaytadi
    __slots__ = ("y",)      ✅ faqat YANGI nomlar
    __slots__ = ("x", "y")  ⚠️ x takrorlandi — behuda

Qachon

✅ 100k+ obyekt
✅ Ma'lumot obyektlari (Nuqta, Yozuv)
✅ Atributlarni cheklash kerak

❌ Kam obyekt (< 100k)
❌ Dinamik atributlar kerak
❌ cached_property, weakref (qo'shmasangiz)
❌ "Ehtiyot uchun" — avval O'LCHANG

4. Batafsil misollar

Misol 1 — Nima qiladi va qancha tejaydi

python
"""__slots__ mexanizmi va o'lchovlar."""

import sys
import timeit
from dataclasses import dataclass

print("=== ⭐ 1. Mexanizm ===\n")


class Oddiy:
    def __init__(self, x, y):
        self.x, self.y = x, y


class Slotli:
    __slots__ = ("x", "y")

    def __init__(self, x, y):
        self.x, self.y = x, y


o, s = Oddiy(1, 2), Slotli(1, 2)

print(f"  Oddiy:")
print(f"    o.__dict__       = {o.__dict__}")
print(f"    type(Oddiy.x)    = ", end="")
try:
    print(type(Oddiy.x).__name__)
except AttributeError:
    print("❌ AttributeError (sinf atributi yo'q)")

print(f"\n  Slotli:")
print(f"    s.__dict__       = ", end="")
try:
    print(s.__dict__)
except AttributeError:
    print("❌ AttributeError")
print(f"    Slotli.__slots__ = {Slotli.__slots__}")
print(f"    type(Slotli.x)   = {type(Slotli.x).__name__}")
print(f"    Slotli.x         = {Slotli.x}")

print(f"\n  ⭐ member_descriptor — DATA deskriptor:")
for m in ["__get__", "__set__", "__delete__"]:
    print(f"    {m:<14} {hasattr(type(Slotli.x), m)}")

print(f"\n  Qo'lda chaqirish:")
print(f"    Slotli.x.__get__(s, Slotli) = {Slotli.x.__get__(s, Slotli)}")
Slotli.x.__set__(s, 99)
print(f"    Slotli.x.__set__(s, 99) → s.x = {s.x}")
s.x = 1

print(f"""
  ⭐ TUZILISH:

     __dict__ bilan:
       obyekt → __dict__ ko'rsatkichi → dict → {{'x': 1, 'y': 2}}
       (ikki bosqichli qidiruv + dict ustama xarajati)

     __slots__ bilan:
       obyekt → [x_ko'rsatkichi, y_ko'rsatkichi]
       (to'g'ridan-to'g'ri OFFSET bo'yicha)
""")


print("=== 2. Xotira o'lchovi ===\n")


def olcham(obj, n: int = 20_000) -> int:
    """Bitta obyektning haqiqiy hajmi (bayt) — tracemalloc bilan.

    ⚠️ sys.getsizeof(obj) + sys.getsizeof(obj.__dict__) Python 3.11+ da
       NOTO'G'RI: obj.__dict__ ga murojaat qilishning o'zi lug'atni
       yaratadi. Shuning uchun obyekt holatidan n ta yangi nusxa yasab,
       o'rtacha o'sishni o'lchaymiz (nusxalarning __dict__ iga tegmaymiz).
    """
    import tracemalloc
    cls = type(obj)
    holat = dict(getattr(obj, "__dict__", {}))
    for S in cls.__mro__:
        slotlar = getattr(S, "__slots__", ())
        if isinstance(slotlar, str):
            slotlar = (slotlar,)
        for nom in slotlar:
            if nom not in ("__dict__", "__weakref__") and hasattr(obj, nom):
                holat[nom] = getattr(obj, nom)
    joy = [None] * n
    tracemalloc.start()
    asos = tracemalloc.get_traced_memory()[0]
    for i in range(n):
        o = cls.__new__(cls)
        for k, v in holat.items():
            object.__setattr__(o, k, v)
        joy[i] = o
    jami = tracemalloc.get_traced_memory()[0] - asos
    tracemalloc.stop()
    return round(jami / n)


def sinf_yasa(n: int, slotli: bool):
    nomlar = tuple(f"a{i}" for i in range(n))
    tana = {"__init__": lambda self, *a: [
        setattr(self, nom, q) for nom, q in zip(nomlar, a)
    ] and None}
    if slotli:
        tana["__slots__"] = nomlar
    return type(f"{'S' if slotli else 'O'}{n}", (), tana)


print(f"  {'Atribut':<10} {'__dict__':<14} {'__slots__':<14} "
      f"{'Tejash':<10} {'1M nusxa farqi'}")
print("  " + "─" * 68)

for n in [1, 2, 3, 5, 10, 20]:
    O = sinf_yasa(n, False)
    S = sinf_yasa(n, True)
    args = tuple(range(n))
    oo, ss = olcham(O(*args)), olcham(S(*args))
    tejash = (1 - ss / oo) * 100
    farq = (oo - ss) * 1_000_000 / 1_048_576
    print(f"  {n:<10} {oo:<14} {ss:<14} {tejash:>6.0f}%    "
          f"{farq:>8.0f} MB")

print(f"""
  ⚠️ Python 3.11+ da "key-sharing dictionaries":
     Bir sinfning barcha nusxalari KALITLARNI ulashadi
     → __dict__ ustama xarajati kamaydi
     → __slots__ foydasi ham kamaydi

  Python versiyasi: {sys.version_info.major}.{sys.version_info.minor}
""")


print("=== 3. Haqiqiy ro'yxatda ===\n")

import tracemalloc


def olcha(Sinf, n: int = 100_000) -> float:
    tracemalloc.start()
    obyektlar = [Sinf(i, i * 2) for i in range(n)]
    joriy, cho_qqi = tracemalloc.get_traced_memory()
    tracemalloc.stop()
    del obyektlar
    return joriy / 1_048_576


@dataclass
class DcOddiy:
    x: int
    y: int


@dataclass(slots=True)
class DcSlotli:
    x: int
    y: int


from typing import NamedTuple


class NtNuqta(NamedTuple):
    x: int
    y: int


SINFLAR = [
    ("Oddiy sinf", Oddiy),
    ("__slots__", Slotli),
    ("dataclass", DcOddiy),
    ("dataclass(slots)", DcSlotli),
    ("NamedTuple", NtNuqta),
    ("tuple", lambda x, y: (x, y)),
]

print(f"  100 000 obyekt (2 atribut):\n")
print(f"  {'Tur':<20} {'Xotira':>10} {'Nisbat'}")
print("  " + "─" * 42)
asos = None
for nom, Sinf in SINFLAR:
    mb = olcha(Sinf)
    if asos is None:
        asos = mb
    print(f"  {nom:<20} {mb:>8.1f} MB {mb / asos:>8.2f}×")


print("\n\n=== 4. Tezlik ===\n")

o, s = Oddiy(1, 2), Slotli(1, 2)
N = 2_000_000

OLCHOVLAR = [
    ("Atribut o'qish",
     lambda: o.x, lambda: s.x),
    ("Atribut yozish",
     lambda: setattr(o, "x", 1), lambda: setattr(s, "x", 1)),
    ("Obyekt yaratish",
     lambda: Oddiy(1, 2), lambda: Slotli(1, 2)),
]

print(f"  {N:,} amal:\n")
print(f"  {'Amal':<20} {'__dict__':>12} {'__slots__':>12} {'Farq'}")
print("  " + "─" * 56)
for nom, f_o, f_s in OLCHOVLAR:
    t1 = timeit.timeit(f_o, number=N)
    t2 = timeit.timeit(f_s, number=N)
    farq = (t1 / t2 - 1) * 100
    print(f"  {nom:<20} {t1:>10.3f}s {t2:>10.3f}s "
          f"{farq:>+6.0f}%")

print(f"""
  ⭐ Nazariyada __slots__ tezroq bo'lishi kerak:
     • dict qidiruvi (xesh) o'rniga belgilangan OFFSET
     • dict yaratilmaydi

  ⚠️ Amalda Python 3.11+ da farq bir necha foiz va ikki tomonga
     ham chiqishi mumkin: ixtisoslashgan bayt-kod (PEP 659) oddiy
     sinf atributlarini ham xuddi shunday tez o'qiydi. O'lchovni
     bir necha marta takrorlasangiz, belgisi ham o'zgarishi mumkin.
     __slots__ ni tezlik uchun tanlamang.
""")


print("=== ⚠️ 5. Cheklovlar ===\n")

s = Slotli(1, 2)

CHEKLOVLAR = [
    ("s.z = 3",             lambda: setattr(s, "z", 3)),
    ("s.__dict__",          lambda: s.__dict__),
    ("vars(s)",             lambda: vars(s)),
    ("s.__weakref__",       lambda: s.__weakref__),
]

for kod, f in CHEKLOVLAR:
    try:
        n = f"⚠️ {f()!r}"
    except (AttributeError, TypeError) as e:
        n = f"✅ {type(e).__name__}: {str(e)[:44]}"
    print(f"  {kod:<20} {n}")

import weakref

print(f"\n  weakref:")
try:
    weakref.ref(s)
    print(f"    weakref.ref(s) → ⚠️ o'tdi")
except TypeError as e:
    print(f"    weakref.ref(s) → ✅ TypeError: {e}")


class SlotliWeakref:
    __slots__ = ("x", "y", "__weakref__")       # ⭐

    def __init__(self, x, y): self.x, self.y = x, y


sw = SlotliWeakref(1, 2)
print(f"    __slots__ ga '__weakref__' qo'shildi:")
print(f"      weakref.ref(sw) → ✅ {weakref.ref(sw)}")

from functools import cached_property

print(f"\n  cached_property:")


class SlotliKesh:
    __slots__ = ("x",)

    def __init__(self, x): self.x = x

    @cached_property
    def kvadrat(self): return self.x ** 2


try:
    SlotliKesh(5).kvadrat
except TypeError as e:
    print(f"    ❌ TypeError: {str(e)[:60]}")


class SlotliKeshTogri:
    __slots__ = ("x", "__dict__")               # ⭐ ikkalasi

    def __init__(self, x): self.x = x

    @cached_property
    def kvadrat(self): return self.x ** 2


k = SlotliKeshTogri(5)
print(f"\n    __dict__ qo'shilgan: kvadrat = {k.kvadrat}")
print(f"      k.__dict__ = {k.__dict__}")
print(f"      ⚠️ Lekin tejash yo'qoldi: "
      f"{olcham(k)} vs {olcham(Slotli(1, 2))} bayt")

print(f"\n  Sinf atributi bilan konflikt:")
try:
    class Konflikt:
        __slots__ = ("x",)
        x = 5
except ValueError as e:
    print(f"    ❌ ValueError: {e}")

print(f"""
  ⭐ SABAB: x = 5 sinf atributi member_descriptor ni
     TO'SIB QO'YARDI — slot ishlamay qolardi.
""")

Natijaning muhim qismi:

text
=== ⭐ 1. Mexanizm ===

  Slotli:
    s.__dict__       = ❌ AttributeError
    type(Slotli.x)   = member_descriptor

  ⭐ member_descriptor — DATA deskriptor:
    __get__        True
    __set__        True

=== 2. Xotira o'lchovi ===

  Atribut    __dict__       __slots__      Tejash     1M nusxa farqi
  ────────────────────────────────────────────────────────────────────
  1          80             40                 50%          38 MB
  3          96             56                 42%          38 MB
  10         160            112                30%          46 MB

=== 3. Haqiqiy ro'yxatda ===

  Tur                      Xotira   Nisbat
  ──────────────────────────────────────────
  Oddiy sinf               15.3 MB     1.00×
  __slots__                11.4 MB     0.75×
  dataclass(slots)         11.4 MB     0.75×
  NamedTuple               13.7 MB     0.90×

=== ⚠️ 5. Cheklovlar ===

  s.z = 3              ✅ AttributeError: 'Slotli' object has no
                          attribute 'z'
  weakref.ref(s) → ✅ TypeError: cannot create weak reference

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

Misol 2 — Meros qoidalari

python
"""__slots__ va meros — murakkab qoidalar."""

import sys

print("=== ⚠️ 1. Avlodda __slots__ yo'q ===\n")


class Asos:
    __slots__ = ("x", "y")

    def __init__(self, x, y): self.x, self.y = x, y


class AvlodSlotsiz(Asos):
    """⚠️ __slots__ YO'Q → __dict__ qaytadi."""
    pass


class AvlodBosh(Asos):
    """✅ Bo'sh __slots__ — tejash saqlanadi."""
    __slots__ = ()


class AvlodYangi(Asos):
    """✅ Faqat YANGI nomlar."""
    __slots__ = ("z",)

    def __init__(self, x, y, z):
        super().__init__(x, y)
        self.z = z


def olcham(obj, n: int = 20_000) -> int:
    """Bitta obyektning haqiqiy hajmi (bayt) — tracemalloc bilan.

    ⚠️ sys.getsizeof(obj) + sys.getsizeof(obj.__dict__) Python 3.11+ da
       NOTO'G'RI: obj.__dict__ ga murojaat qilishning o'zi lug'atni
       yaratadi. Shuning uchun obyekt holatidan n ta yangi nusxa yasab,
       o'rtacha o'sishni o'lchaymiz (nusxalarning __dict__ iga tegmaymiz).
    """
    import tracemalloc
    cls = type(obj)
    holat = dict(getattr(obj, "__dict__", {}))
    for S in cls.__mro__:
        slotlar = getattr(S, "__slots__", ())
        if isinstance(slotlar, str):
            slotlar = (slotlar,)
        for nom in slotlar:
            if nom not in ("__dict__", "__weakref__") and hasattr(obj, nom):
                holat[nom] = getattr(obj, nom)
    joy = [None] * n
    tracemalloc.start()
    asos = tracemalloc.get_traced_memory()[0]
    for i in range(n):
        o = cls.__new__(cls)
        for k, v in holat.items():
            object.__setattr__(o, k, v)
        joy[i] = o
    jami = tracemalloc.get_traced_memory()[0] - asos
    tracemalloc.stop()
    return round(jami / n)


print(f"  {'Sinf':<18} {'__dict__':<12} {'Yangi atribut':<18} "
      f"{'Bayt'}")
print("  " + "─" * 62)
for Sinf, args in [(Asos, (1, 2)), (AvlodSlotsiz, (1, 2)),
                   (AvlodBosh, (1, 2)), (AvlodYangi, (1, 2, 3))]:
    o = Sinf(*args)
    d = "✅ bor" if hasattr(o, "__dict__") else "❌ yo'q"
    try:
        o.qoshimcha = 1
        y = "⚠️ mumkin"
    except AttributeError:
        y = "✅ to'sildi"
    print(f"  {Sinf.__name__:<18} {d:<12} {y:<18} {olcham(o)}")

print(f"""
  ⭐ QOIDA: HAR avlodda __slots__ yozing.
     Bo'sh bo'lsa ham: __slots__ = ()

  ⚠️ Aks holda __dict__ qaytadi va tejash YO'QOLADI.
""")


print("=== ⚠️ 2. Takrorlash — behuda xotira ===\n")


class Takrorli(Asos):
    __slots__ = ("x", "y", "z")                 # ⚠️ x, y takror

    def __init__(self, x, y, z):
        super().__init__(x, y)
        self.z = z


class Togri(Asos):
    __slots__ = ("z",)                          # ✅ faqat yangi

    def __init__(self, x, y, z):
        super().__init__(x, y)
        self.z = z


print(f"  {'Sinf':<14} {'__slots__':<20} {'Jami slotlar':<14} {'Bayt'}")
print("  " + "─" * 62)
for Sinf in (Togri, Takrorli):
    o = Sinf(1, 2, 3)
    jami = sum(len(getattr(S, "__slots__", ()))
               for S in Sinf.__mro__)
    print(f"  {Sinf.__name__:<14} {str(Sinf.__slots__):<20} "
          f"{jami:<14} {olcham(o)}")

print(f"\n  ⭐ Takrorli da x va y IKKI marta joy egallaydi")
print(f"     (ota-sinfda va avlodda)")

t = Takrorli(1, 2, 3)
print(f"\n  ⚠️ Asos.__init__ ichidagi self.x = x qaysi slotga yozdi?")
print(f"    Takrorli.x.__get__(t, Takrorli) = "
      f"{Takrorli.x.__get__(t, Takrorli)}   ⭐ avlod sloti")
try:
    qiymat = Asos.x.__get__(t, Takrorli)
except AttributeError as e:
    qiymat = f"AttributeError: {e}"
print(f"    Asos.x.__get__(t, Takrorli)     = {qiymat}")
print(f"    t.x                             = {t.x}")
print(f"""
  ⭐ self.x = x MRO bo'yicha BIRINCHI topilgan deskriptorga yozadi —
     bu Takrorli.x. Ota-sinfning Asos.x sloti hech qachon to'ldirilmaydi:
     u xotirada joy egallaydi, lekin doim BO'SH qoladi.
""")


print("\n\n=== ⚠️ 3. __dict__ bo'lgan ota-sinf ===\n")


class OtaDict:
    """__slots__ YO'Q → __dict__ bor."""
    def __init__(self, a): self.a = a


class BolaSlots(OtaDict):
    """⚠️ __slots__ FOYDASIZ — __dict__ meros olinadi."""
    __slots__ = ("b",)

    def __init__(self, a, b):
        super().__init__(a)
        self.b = b


b = BolaSlots(1, 2)
print(f"  class OtaDict:  (slots yo'q)")
print(f"  class BolaSlots(OtaDict): __slots__ = ('b',)\n")
print(f"    b.__dict__     = {b.__dict__}")
print(f"    b.qoshimcha = 1 → ", end="")
b.qoshimcha = 1
print(f"✅ mumkin (⚠️)")
print(f"    b.__dict__     = {b.__dict__}")
print(f"    Bayt: {olcham(b)}")

print(f"""
  ⚠️ __slots__ FAQAT butun ierarxiya bo'ylab ishlaganda foydali.

     object → A(slots) → B(slots) → C(slots)   ✅ tejash
     object → A(dict)  → B(slots)              ❌ foydasiz
""")


print("=== ⚠️ 4. Ko'p meros ===\n")


class A1:
    __slots__ = ("x",)


class A2:
    __slots__ = ("y",)


class B1:
    __slots__ = ()                              # ⭐ bo'sh


class B2:
    __slots__ = ()


HOLATLAR = [
    ("class C(A1, A2)", (A1, A2)),
    ("class C(A1, B1)", (A1, B1)),
    ("class C(B1, B2)", (B1, B2)),
    ("class C(A1, B1, B2)", (A1, B1, B2)),
]

for tavsif, otalar in HOLATLAR:
    try:
        C = type("C", otalar, {"__slots__": ()})
        n = "✅ yaratildi"
    except TypeError as e:
        n = f"❌ TypeError: {str(e)[:44]}"
    print(f"  {tavsif:<24} {n}")

print(f"""
  ⭐ QOIDA: ko'p merosda FAQAT BITTA ota-sinfda
     bo'sh bo'lmagan __slots__ bo'lishi mumkin.

  ⚠️ SABAB: har slot obyektda ANIQ OFFSET egallaydi.
     Ikki ota-sinf bir offsetni talab qilsa — konflikt.

  ✅ MIXIN lar uchun __slots__ = () yozing:
""")


class JurnalMixin:
    __slots__ = ()                              # ⭐ mixin

    def yoz(self, x): print(f"      [jurnal] {x}")


class KeshMixin:
    __slots__ = ()                              # ⭐

    def kesh_ol(self, k): return None


class Xizmat(JurnalMixin, KeshMixin):
    __slots__ = ("nom", "port")

    def __init__(self, nom, port):
        self.nom, self.port = nom, port


x = Xizmat("api", 8000)
print(f"  Xizmat(JurnalMixin, KeshMixin): ✅ yaratildi")
print(f"    x.nom = {x.nom}, bayt = {olcham(x)}")
print(f"    __dict__ bormi: {hasattr(x, '__dict__')}")
x.yoz("test")


print("\n\n=== 5. Chuqur ierarxiya ===\n")


class D1:
    __slots__ = ("a",)


class D2(D1):
    __slots__ = ("b",)


class D3(D2):
    __slots__ = ("c",)


class D4(D3):
    __slots__ = ("d",)


d = D4()
d.a, d.b, d.c, d.d = 1, 2, 3, 4

print(f"  D1(a) → D2(b) → D3(c) → D4(d)\n")
print(f"    d.a, d.b, d.c, d.d = {d.a}, {d.b}, {d.c}, {d.d}")
print(f"    __dict__ bormi: {hasattr(d, '__dict__')}")
print(f"    Bayt: {olcham(d)}")

print(f"\n  Barcha slotlar:")
for S in D4.__mro__:
    sl = getattr(S, "__slots__", None)
    if sl is not None:
        print(f"    {S.__name__:<10} {sl}")


def barcha_slotlar(Sinf) -> tuple[str, ...]:
    """⭐ MRO bo'ylab barcha slotlarni yig'adi."""
    natija = []
    for S in reversed(Sinf.__mro__):
        sl = getattr(S, "__slots__", ())
        if isinstance(sl, str):
            sl = (sl,)
        natija.extend(s for s in sl if s not in natija
                      and not s.startswith("__"))
    return tuple(natija)


print(f"\n  barcha_slotlar(D4) = {barcha_slotlar(D4)}")


print("\n\n=== 6. Pickle va copy ===\n")

import pickle
import copy


class Slotli2:
    __slots__ = ("x", "y")

    def __init__(self, x, y): self.x, self.y = x, y

    def __repr__(self): return f"Slotli2({self.x}, {self.y})"

    def __eq__(self, b):
        return (isinstance(b, Slotli2)
                and (self.x, self.y) == (b.x, b.y))


s = Slotli2(1, [2, 3])

print(f"  s = {s}\n")

AMALLAR = [
    ("pickle (protokol 2)",
     lambda: pickle.loads(pickle.dumps(s, protocol=2))),
    ("pickle (sukut)",
     lambda: pickle.loads(pickle.dumps(s))),
    ("copy.copy",       lambda: copy.copy(s)),
    ("copy.deepcopy",   lambda: copy.deepcopy(s)),
]

for nom, f in AMALLAR:
    try:
        n = f()
        print(f"  {nom:<24} ✅ {n} (teng: {n == s})")
    except Exception as e:
        print(f"  {nom:<24} ❌ {type(e).__name__}: {str(e)[:36]}")

print(f"\n  ⭐ __getstate__/__setstate__ bilan nazorat:")


class Nazoratli:
    __slots__ = ("x", "_kesh")

    def __init__(self, x):
        self.x = x
        self._kesh = {"qimmat": "hisoblangan"}

    def __getstate__(self):
        """⭐ _kesh saqlanmaydi."""
        return {"x": self.x}

    def __setstate__(self, holat):
        self.x = holat["x"]
        self._kesh = {}                         # ⭐ qayta yaratiladi

    def __repr__(self):
        return f"Nazoratli({self.x}, kesh={self._kesh})"


n = Nazoratli(42)
print(f"    Asl:       {n}")
n2 = pickle.loads(pickle.dumps(n))
print(f"    Pickle'dan: {n2}")
print(f"    ⭐ _kesh saqlanmadi, qayta yaratildi")


print("\n\n=== 7. Tashxis vositasi ===\n")


def slots_tahlil(Sinf: type) -> dict:
    """__slots__ holatini tahlil qiladi."""
    ozi = getattr(Sinf, "__slots__", None)
    if isinstance(ozi, str):
        ozi = (ozi,)

    # __dict__ bormi
    dict_bor = any(
        "__dict__" in getattr(S, "__slots__", ())
        or (S is not object and not hasattr(S, "__slots__"))
        for S in Sinf.__mro__
    )

    # Takrorlar
    korilgan: dict[str, list[str]] = {}
    for S in Sinf.__mro__:
        sl = getattr(S, "__slots__", ())
        if isinstance(sl, str):
            sl = (sl,)
        for s in sl:
            korilgan.setdefault(s, []).append(S.__name__)
    takrorlar = {k: v for k, v in korilgan.items() if len(v) > 1}

    muammolar = []
    if ozi is None:
        muammolar.append("__slots__ yo'q")
    if dict_bor and ozi is not None:
        muammolar.append("__dict__ bor — tejash yo'q")
    if takrorlar:
        muammolar.append(f"takrorlangan slotlar: {sorted(takrorlar)}")
    if "__weakref__" not in korilgan and ozi is not None and not dict_bor:
        muammolar.append("weakref ishlamaydi")

    return {
        "ozi": ozi,
        "jami": sorted(korilgan),
        "dict_bor": dict_bor,
        "takrorlar": takrorlar,
        "muammolar": muammolar,
    }


SINFLAR = [Asos, AvlodSlotsiz, AvlodBosh, Takrorli, BolaSlots, D4,
           Xizmat, SlotliWeakref if "SlotliWeakref" in dir() else Asos]

for Sinf in SINFLAR[:7]:
    n = slots_tahlil(Sinf)
    print(f"  {Sinf.__name__}:")
    print(f"    O'z slotlari: {n['ozi']}")
    print(f"    Jami:         {[s for s in n['jami'] if not s.startswith('__')]}")
    if n["muammolar"]:
        for m in n["muammolar"]:
            print(f"    ⚠️ {m}")
    else:
        print(f"    ✅ muammo yo'q")
    print()

Natijaning muhim qismi:

text
=== ⚠️ 1. Avlodda __slots__ yo'q ===

  Sinf               __dict__     Yangi atribut      Bayt
  ──────────────────────────────────────────────────────────────
  Asos               ❌ yo'q      ✅ to'sildi        48
  AvlodSlotsiz       ✅ bor        ⚠️ mumkin          96
  AvlodBosh          ❌ yo'q      ✅ to'sildi        48
  AvlodYangi         ❌ yo'q      ✅ to'sildi        56

=== ⚠️ 4. Ko'p meros ===

  class C(A1, A2)          ❌ TypeError: multiple bases have instance lay-out conflic
  class C(A1, B1)          ✅ yaratildi
  class C(B1, B2)          ✅ yaratildi

=== 6. Pickle va copy ===

  pickle (protokol 2)      ✅ Slotli2(1, [2, 3]) (teng: True)
  copy.deepcopy            ✅ Slotli2(1, [2, 3]) (teng: True)

Nima ko'rsatdi: 2.3, 2.4, 2.7-bo'limlar.

Misol 3 — Qachon kerak, qachon kerak emas

python
"""__slots__ ni to'g'ri qo'llash."""

from __future__ import annotations

import sys
import time
import tracemalloc
from dataclasses import dataclass, field
from typing import NamedTuple

print("=== ✅ 1. Kerak: ko'p ma'lumot obyekti ===\n")


class Yozuv:
    """⭐ Jurnal yozuvi — millionlab bo'lishi mumkin."""

    __slots__ = ("vaqt", "daraja", "xabar", "manba")

    def __init__(self, vaqt: float, daraja: str, xabar: str,
                 manba: str = ""):
        self.vaqt = vaqt
        self.daraja = daraja
        self.xabar = xabar
        self.manba = manba

    def __repr__(self):
        return f"Yozuv({self.daraja}, {self.xabar[:20]!r})"


class YozuvOddiy:
    def __init__(self, vaqt, daraja, xabar, manba=""):
        self.vaqt = vaqt
        self.daraja = daraja
        self.xabar = xabar
        self.manba = manba


def olcha(Sinf, n: int) -> tuple[float, float]:
    tracemalloc.start()
    b = time.perf_counter()
    obyektlar = [
        Sinf(i * 0.001, "INFO", f"Xabar raqami {i}", "modul.py")
        for i in range(n)
    ]
    vaqt = time.perf_counter() - b
    joriy, _ = tracemalloc.get_traced_memory()
    tracemalloc.stop()
    del obyektlar
    return joriy / 1_048_576, vaqt


N = 200_000
print(f"  {N:,} jurnal yozuvi:\n")
print(f"  {'Tur':<20} {'Xotira':>10} {'Vaqt':>10} {'Nisbat'}")
print("  " + "─" * 52)

m1, t1 = olcha(YozuvOddiy, N)
m2, t2 = olcha(Yozuv, N)
print(f"  {'Oddiy sinf':<20} {m1:>8.1f} MB {t1:>8.3f}s  1.00×")
print(f"  {'__slots__':<20} {m2:>8.1f} MB {t2:>8.3f}s  {m2/m1:>4.2f}×")
print(f"\n  ⭐ Tejash: {m1 - m2:.1f} MB ({(1 - m2/m1) * 100:.0f}%)")


print("\n\n=== ❌ 2. Kerak emas: kam obyekt ===\n")

print("""  ⚠️ Bu holatlarda __slots__ FOYDASIZ:

     class Sozlama:              # 1 nusxa
         __slots__ = (...)       ⚠️ 100 bayt tejaldi

     class Xizmat:               # 5-10 nusxa
         __slots__ = (...)       ⚠️ 1 KB tejaldi

     class Ulanish:              # 50 nusxa
         __slots__ = (...)       ⚠️ 5 KB tejaldi

  ⭐ MEZON: 100 000 dan kam obyekt bo'lsa —
     tejash sezilmaydi, lekin cheklovlar qoladi.
""")

print(f"  Tejash miqdori (~40 bayt/obyekt, 2.5-bo'lim jadvali):\n")
print(f"  {'Obyektlar soni':<20} {'Tejash'}")
print("  " + "─" * 36)
for n, izoh in [(1, "sozlama"), (10, "xizmatlar"),
                (1_000, "foydalanuvchilar"),
                (100_000, "yozuvlar"), (10_000_000, "nuqtalar")]:
    tejash = n * 40
    if tejash < 1024:
        s = f"{tejash} bayt"
    elif tejash < 1_048_576:
        s = f"{tejash / 1024:.1f} KB"
    else:
        s = f"{tejash / 1_048_576:.1f} MB"
    belgi = "⚠️" if n < 100_000 else "✅"
    print(f"  {f'{n:,} ({izoh})':<20} {s:<12} {belgi}")


print("\n\n=== ✅ 3. Kerak: atributlarni cheklash ===\n")


class SozlamaQatiy:
    """⭐ __slots__ — xato nomni to'sadi."""

    __slots__ = ("host", "port", "debug", "vaqt_chegarasi")

    def __init__(self, host="localhost", port=8000, debug=False,
                 vaqt_chegarasi=30.0):
        self.host = host
        self.port = port
        self.debug = debug
        self.vaqt_chegarasi = vaqt_chegarasi


class SozlamaOddiy:
    def __init__(self, host="localhost", port=8000, debug=False,
                 vaqt_chegarasi=30.0):
        self.host = host
        self.port = port
        self.debug = debug
        self.vaqt_chegarasi = vaqt_chegarasi


print(f"  Xato yozilgan atribut nomi:\n")
for Sinf in (SozlamaOddiy, SozlamaQatiy):
    s = Sinf()
    try:
        s.pott = 9000                           # ⚠️ port emas, pott
        n = "⚠️ o'tdi — xato JIMGINA"
    except AttributeError as e:
        n = f"✅ AttributeError: {e}"
    print(f"    {Sinf.__name__:<16} s.pott = 9000 → {n}")

s = SozlamaOddiy()
s.pott = 9000
print(f"\n    SozlamaOddiy: s.port = {s.port}   ⚠️ o'zgarmadi!")
print(f"                  s.pott = {s.pott}   ⚠️ yangi atribut")

print(f"""
  ⭐ __slots__ — XATO NOMLARDAN himoya:
     • Konfiguratsiya sinflari
     • Ma'lumot modellari
     • API javoblari
     • Holat mashinalari

  ⚠️ Muqobil: __setattr__ yoki frozen dataclass
""")


print("=== ⚠️ 4. Ishlamaydigan holatlar ===\n")

from functools import cached_property
import weakref

MUAMMOLAR = [
    ("cached_property", """
     class A:
         __slots__ = ("x",)
         @cached_property
         def y(self): ...        ❌ TypeError

     ✅ __slots__ = ("x", "__dict__")
        yoki oddiy @property"""),

    ("weakref", """
     weakref.ref(obj)            ❌ TypeError

     ✅ __slots__ = ("x", "__weakref__")
        yoki @dataclass(slots=True, weakref_slot=True)"""),

    ("Dinamik atributlar", """
     obj.yangi = 1               ❌ AttributeError

     ✅ __slots__ = ("x", "__dict__")
        yoki umuman __slots__ ishlatmang"""),

    ("Ko'p meros", """
     class C(A_slots, B_slots)   ❌ TypeError

     ✅ Mixin larda __slots__ = ()"""),

    ("Sinf atributi sukuti", """
     class A:
         __slots__ = ("x",)
         x = 5                   ❌ ValueError

     ✅ __init__ da o'rnating"""),

    ("Monkey patching", """
     obj.yangi_metod = f         ❌ AttributeError

     ✅ Sinfga qo'shing: A.yangi_metod = f"""),
]

for nom, izoh in MUAMMOLAR:
    print(f"  ⚠️ {nom}:{izoh}\n")


print("=== 5. Muqobillar ===\n")


@dataclass
class DcOddiy:
    x: int
    y: int
    z: int


@dataclass(slots=True)
class DcSlots:
    x: int
    y: int
    z: int


@dataclass(frozen=True, slots=True)
class DcFrozen:
    x: int
    y: int
    z: int


class NtNuqta(NamedTuple):
    x: int
    y: int
    z: int


class SlotsQolda:
    __slots__ = ("x", "y", "z")

    def __init__(self, x, y, z):
        self.x, self.y, self.z = x, y, z


VARIANTLAR = [
    ("dataclass", DcOddiy),
    ("dataclass(slots)", DcSlots),
    ("dataclass(frozen+slots)", DcFrozen),
    ("NamedTuple", NtNuqta),
    ("Qo'lda __slots__", SlotsQolda),
    ("tuple", lambda x, y, z: (x, y, z)),
    ("dict", lambda x, y, z: {"x": x, "y": y, "z": z}),
]


def olcha2(Sinf, n=100_000):
    tracemalloc.start()
    o = [Sinf(i, i, i) for i in range(n)]
    joriy, _ = tracemalloc.get_traced_memory()
    tracemalloc.stop()
    del o
    return joriy / 1_048_576


print(f"  100 000 obyekt (3 atribut):\n")
print(f"  {'Variant':<26} {'Xotira':>10} {'O`zgaruvchan':<14} "
      f"{'Nom bilan'}")
print("  " + "─" * 64)
for nom, Sinf in VARIANTLAR:
    mb = olcha2(Sinf)
    o = Sinf(1, 2, 3)
    try:
        if isinstance(o, dict):
            o["x"] = 9
        else:
            o.x = 9
        ozg = "✅"
    except (AttributeError, TypeError):
        ozg = "❌"
    nomli = "✅" if hasattr(o, "x") or isinstance(o, dict) else "❌"
    print(f"  {nom:<26} {mb:>8.1f} MB {ozg:<14} {nomli}")

print(f"""
  ⭐ TANLASH:

     Eng kam xotira                → dataclass(slots=True) / __slots__
     O'zgarmas, indeks/ochib olish → NamedTuple (xotira uchun EMAS)
     Kam xotira + hashable         → dataclass(frozen=True, slots=True)
     Moslashuvchan                 → oddiy dataclass
     Dinamik kalitlar              → dict
""")


print("=== ⭐ 6. Amaliy: zarrachalar tizimi ===\n")


class Zarracha:
    """⭐ Millionlab zarracha — __slots__ zarur."""

    __slots__ = ("x", "y", "vx", "vy", "umr", "rang")

    def __init__(self, x: float, y: float, vx: float = 0.0,
                 vy: float = 0.0, umr: float = 1.0,
                 rang: int = 0xFFFFFF):
        self.x, self.y = x, y
        self.vx, self.vy = vx, vy
        self.umr = umr
        self.rang = rang

    def yangila(self, dt: float, tortishish: float = -9.81) -> bool:
        """Holatni yangilaydi. Tirikligini qaytaradi."""
        self.x += self.vx * dt
        self.y += self.vy * dt
        self.vy += tortishish * dt
        self.umr -= dt
        return self.umr > 0

    def __repr__(self):
        return (f"Zarracha({self.x:.1f}, {self.y:.1f}, "
                f"umr={self.umr:.2f})")


class ZarrachaTizimi:
    """⭐ O'zi ko'p emas — __slots__ shart emas, lekin foydali."""

    __slots__ = ("_zarrachalar", "tortishish", "_yaratilgan")

    def __init__(self, tortishish: float = -9.81):
        self._zarrachalar: list[Zarracha] = []
        self.tortishish = tortishish
        self._yaratilgan = 0

    def qosh(self, n: int, **kw) -> None:
        import random
        for _ in range(n):
            self._zarrachalar.append(Zarracha(
                x=kw.get("x", 0.0),
                y=kw.get("y", 0.0),
                vx=random.uniform(-5, 5),
                vy=random.uniform(0, 10),
                umr=random.uniform(0.5, 2.0),
            ))
        self._yaratilgan += n

    def yangila(self, dt: float) -> int:
        """Barchasini yangilaydi, o'lganlarni olib tashlaydi."""
        self._zarrachalar = [
            z for z in self._zarrachalar if z.yangila(dt, self.tortishish)
        ]
        return len(self._zarrachalar)

    def __len__(self):
        return len(self._zarrachalar)

    def statistika(self) -> dict:
        if not self._zarrachalar:
            return {"tirik": 0, "yaratilgan": self._yaratilgan}
        return {
            "tirik": len(self._zarrachalar),
            "yaratilgan": self._yaratilgan,
            "ortacha_umr": sum(z.umr for z in self._zarrachalar)
                           / len(self._zarrachalar),
            "eng_baland": max(z.y for z in self._zarrachalar),
        }


import random
random.seed(42)

t = ZarrachaTizimi()
t.qosh(500_000)

print(f"  500 000 zarracha yaratildi\n")

tracemalloc.start()
t2 = ZarrachaTizimi()
t2.qosh(200_000)
joriy, _ = tracemalloc.get_traced_memory()
tracemalloc.stop()
print(f"  200 000 zarracha xotirasi: {joriy / 1_048_576:.1f} MB")

print(f"\n  Simulyatsiya:")
print(f"    {'Qadam':<8} {'Tirik':<12} {'Vaqt':<10} {'Eng baland'}")
print("  " + "─" * 46)
for qadam in range(1, 6):
    b = time.perf_counter()
    tirik = t.yangila(0.2)
    vaqt = time.perf_counter() - b
    s = t.statistika()
    baland = s.get("eng_baland", 0)
    print(f"    {qadam:<8} {tirik:<12,} {vaqt:>7.3f}s   "
          f"{baland:>8.2f}")

print(f"\n  Yakuniy: {t.statistika()}")

print(f"""

  ⭐ NEGA BU YERDA __slots__ ZARUR:

     • 500 000 zarracha × 6 atribut
     • __dict__ bilan: ~57 MB (har biri ~120 bayt)
     • __slots__ bilan: ~38 MB (har biri ~80 bayt)
     • Atribut kirishi tez-tez (har qadamda 6 × N)

  ⭐ ZarrachaTizimi da ham __slots__ bor, lekin
     bu tejash uchun emas — XATO NOMLARDAN himoya uchun.
""")

Natijaning muhim qismi:

text
=== ✅ 1. Kerak: ko'p ma'lumot obyekti ===

  200,000 jurnal yozuvi:

  Tur                      Xotira       Vaqt  Nisbat
  ────────────────────────────────────────────────────
  Oddiy sinf               37.3 MB    0.764s  1.00×
  __slots__                29.7 MB    0.775s  0.80×

  ⭐ Tejash: 7.6 MB (20%)

=== ✅ 3. Kerak: atributlarni cheklash ===

    SozlamaOddiy     s.pott = 9000 → ⚠️ o'tdi — xato JIMGINA
    SozlamaQatiy     s.pott = 9000 → ✅ AttributeError

=== 5. Muqobillar ===

  Variant                        Xotira O`zgaruvchan   Nom bilan
  ────────────────────────────────────────────────────────────────
  dataclass                      13.0 MB ✅              ✅
  dataclass(slots)                9.1 MB ✅              ✅
  NamedTuple                     11.4 MB ❌              ✅
  tuple                          10.7 MB ❌              ❌

Nima ko'rsatdi: 2.3, 2.5, 2.7-bo'limlar.

Misol 4 — Amaliy: geometrik dvigatel

python
"""__slots__ bilan ishlash tizimi."""

from __future__ import annotations

import math
import sys
import time
import tracemalloc
import weakref
from dataclasses import dataclass, field
from typing import Iterator, ClassVar

print("=== GEOMETRIK DVIGATEL ===\n")


class Vektor:
    """⭐ Eng ko'p yaratiladigan obyekt — __slots__ zarur."""

    __slots__ = ("x", "y")

    def __init__(self, x: float = 0.0, y: float = 0.0):
        self.x = float(x)
        self.y = float(y)

    # ── Arifmetika (yangi obyektlar) ──

    def __add__(self, b):
        if not isinstance(b, Vektor): return NotImplemented
        return Vektor(self.x + b.x, self.y + b.y)

    def __sub__(self, b):
        if not isinstance(b, Vektor): return NotImplemented
        return Vektor(self.x - b.x, self.y - b.y)

    def __mul__(self, n):
        if isinstance(n, Vektor):
            return self.x * n.x + self.y * n.y       # skalyar ko'paytma
        if isinstance(n, (int, float)):
            return Vektor(self.x * n, self.y * n)
        return NotImplemented

    __rmul__ = __mul__

    def __truediv__(self, n):
        if not isinstance(n, (int, float)): return NotImplemented
        if n == 0: raise ZeroDivisionError("Vektor nolga bo'lindi")
        return Vektor(self.x / n, self.y / n)

    def __neg__(self): return Vektor(-self.x, -self.y)
    def __abs__(self): return math.hypot(self.x, self.y)

    # ── Joyida o'zgartirish (tez) ──

    def __iadd__(self, b):
        """⭐ Yangi obyekt yaratmaydi."""
        if not isinstance(b, Vektor): return NotImplemented
        self.x += b.x
        self.y += b.y
        return self

    def __imul__(self, n):
        if not isinstance(n, (int, float)): return NotImplemented
        self.x *= n
        self.y *= n
        return self

    # ── Boshqalar ──

    def __eq__(self, b):
        return (isinstance(b, Vektor)
                and math.isclose(self.x, b.x)
                and math.isclose(self.y, b.y))

    def __hash__(self): return hash((round(self.x, 9), round(self.y, 9)))
    def __iter__(self): return iter((self.x, self.y))
    def __getitem__(self, i): return (self.x, self.y)[i]
    def __len__(self): return 2
    def __bool__(self): return bool(self.x or self.y)

    def __repr__(self): return f"Vektor({self.x:g}, {self.y:g})"
    def __str__(self): return f"({self.x:g}, {self.y:g})"

    def __format__(self, spec):
        if spec == "u": return f"{abs(self):.4f}"
        if spec == "burchak": return f"{math.degrees(self.burchak):.1f}°"
        if not spec: return str(self)
        return f"({format(self.x, spec)}, {format(self.y, spec)})"

    # ── Xususiyatlar (property — cached_property ISHLAMAYDI) ──

    @property
    def uzunlik(self) -> float:
        return math.hypot(self.x, self.y)

    @property
    def burchak(self) -> float:
        return math.atan2(self.y, self.x)

    def normalla(self) -> "Vektor":
        u = self.uzunlik
        if u == 0: raise ValueError("Nol vektorni normallashtirib bo'lmaydi")
        return Vektor(self.x / u, self.y / u)

    def aylantir(self, burchak: float) -> "Vektor":
        c, s = math.cos(burchak), math.sin(burchak)
        return Vektor(self.x * c - self.y * s, self.x * s + self.y * c)

    # ── Fabrikalar ──

    @classmethod
    def qutbdan(cls, uzunlik: float, burchak: float) -> "Vektor":
        return cls(uzunlik * math.cos(burchak), uzunlik * math.sin(burchak))

    @classmethod
    def nol(cls) -> "Vektor": return cls(0, 0)

    # ── Pickle uchun ──

    def __getstate__(self): return (self.x, self.y)
    def __setstate__(self, holat): self.x, self.y = holat


class Jism:
    """⭐ Fizik jism — ko'p bo'ladi."""

    __slots__ = ("joy", "tezlik", "massa", "_id", "__weakref__")

    _keyingi_id: ClassVar[int] = 1
    _tiriklar: ClassVar[weakref.WeakSet] = weakref.WeakSet()

    def __init__(self, joy: Vektor, tezlik: Vektor | None = None,
                 massa: float = 1.0):
        if massa <= 0:
            raise ValueError(f"Massa musbat bo'lsin: {massa}")
        self.joy = joy
        self.tezlik = tezlik if tezlik is not None else Vektor.nol()
        self.massa = float(massa)
        self._id = Jism._keyingi_id
        Jism._keyingi_id += 1
        Jism._tiriklar.add(self)                # ⭐ __weakref__ kerak

    @property
    def id(self) -> int: return self._id

    @property
    def impuls(self) -> Vektor: return self.tezlik * self.massa

    @property
    def kinetik_energiya(self) -> float:
        return 0.5 * self.massa * self.tezlik.uzunlik ** 2

    def kuch_qoll(self, kuch: Vektor, dt: float) -> None:
        """⭐ Joyida o'zgartirish — yangi obyekt yaratilmaydi."""
        self.tezlik += kuch / self.massa * dt

    def yangila(self, dt: float) -> None:
        self.joy += self.tezlik * dt

    def __repr__(self):
        return (f"Jism(#{self._id}, {self.joy}, m={self.massa:g})")


class Dunyo:
    """Simulyatsiya konteyneri."""

    __slots__ = ("_jismlar", "tortishish", "_qadam")

    def __init__(self, tortishish: Vektor | None = None):
        self._jismlar: list[Jism] = []
        self.tortishish = tortishish or Vektor(0, -9.81)
        self._qadam = 0

    def qosh(self, *jismlar: Jism) -> "Dunyo":
        self._jismlar.extend(jismlar)
        return self

    def yangila(self, dt: float) -> None:
        for j in self._jismlar:
            j.kuch_qoll(self.tortishish * j.massa, dt)
            j.yangila(dt)
            if j.joy.y < 0:                     # yerga urildi
                j.joy.y = 0.0
                j.tezlik.y = -j.tezlik.y * 0.7  # elastiklik
        self._qadam += 1

    def __len__(self): return len(self._jismlar)
    def __iter__(self) -> Iterator[Jism]: return iter(self._jismlar)

    @property
    def jami_energiya(self) -> float:
        return sum(j.kinetik_energiya
                   + j.massa * abs(self.tortishish.y) * j.joy.y
                   for j in self._jismlar)

    def statistika(self) -> dict:
        if not self._jismlar:
            return {"jismlar": 0}
        return {
            "jismlar": len(self._jismlar),
            "qadam": self._qadam,
            "energiya": round(self.jami_energiya, 2),
            "eng_baland": round(max(j.joy.y for j in self._jismlar), 2),
            "eng_tez": round(max(j.tezlik.uzunlik
                                 for j in self._jismlar), 2),
        }


print("1. Vektor amallari:\n")

v1, v2 = Vektor(3, 4), Vektor(1, 2)

AMALLAR = [
    ("v1", v1), ("v2", v2),
    ("v1 + v2", v1 + v2),
    ("v1 - v2", v1 - v2),
    ("v1 * 2", v1 * 2),
    ("2 * v1", 2 * v1),
    ("v1 * v2", v1 * v2),
    ("v1 / 2", v1 / 2),
    ("-v1", -v1),
    ("abs(v1)", abs(v1)),
    ("v1.uzunlik", v1.uzunlik),
    ("v1.normalla()", v1.normalla()),
    ("v1.aylantir(pi/2)", v1.aylantir(math.pi / 2)),
    ("Vektor.qutbdan(5, 0)", Vektor.qutbdan(5, 0)),
    ("tuple(v1)", tuple(v1)),
    ("v1[0]", v1[0]),
    ("len(v1)", len(v1)),
    ("bool(Vektor.nol())", bool(Vektor.nol())),
    ("f'{v1:u}'", f"{v1:u}"),
    ("f'{v1:burchak}'", f"{v1:burchak}"),
    ("f'{v1:.2f}'", f"{v1:.2f}"),
]

for kod, natija in AMALLAR:
    print(f"  {kod:<24} → {natija!r}")


print("\n\n2. ⭐ Xotira:\n")


class VektorOddiy:
    def __init__(self, x, y): self.x, self.y = x, y


def olcha(Sinf, n=300_000):
    tracemalloc.start()
    o = [Sinf(i, i) for i in range(n)]
    joriy, _ = tracemalloc.get_traced_memory()
    tracemalloc.stop()
    del o
    return joriy / 1_048_576


N = 300_000
m1, m2 = olcha(VektorOddiy, N), olcha(Vektor, N)

print(f"  {N:,} vektor:")
print(f"    Oddiy sinf: {m1:.1f} MB")
print(f"    __slots__:  {m2:.1f} MB")
print(f"    ⭐ Tejash:   {m1 - m2:.1f} MB ({(1 - m2/m1)*100:.0f}%)")

print(f"\n  Bitta obyekt:")
print(f"    VektorOddiy: ~{m1 * 1_048_576 / N:.0f} bayt (ro'yxat ko'rsatkichi bilan)")
print(f"    Vektor:      ~{m2 * 1_048_576 / N:.0f} bayt")


print("\n\n3. Tezlik: += vs +\n")

v = Vektor(0, 0)
d = Vektor(1, 1)
N2 = 500_000

t1 = time.perf_counter()
a = Vektor(0, 0)
for _ in range(N2):
    a = a + d                                   # yangi obyekt
t_yangi = time.perf_counter() - t1

t2 = time.perf_counter()
b = Vektor(0, 0)
for _ in range(N2):
    b += d                                      # ⭐ joyida
t_joyida = time.perf_counter() - t2

print(f"  {N2:,} qo'shish:")
print(f"    a = a + d  (yangi):  {t_yangi:.3f}s")
print(f"    b += d     (joyida): {t_joyida:.3f}s")
print(f"    ⭐ {t_yangi / t_joyida:.1f}× tezroq")
print(f"    Natijalar teng: {a == b}")


print("\n\n4. ⭐ __weakref__ bilan kuzatuv:\n")

j1 = Jism(Vektor(0, 10), Vektor(5, 0), massa=2.0)
j2 = Jism(Vektor(0, 20), Vektor(-3, 0), massa=1.5)
j3 = Jism(Vektor(10, 5), massa=0.5)

print(f"  Yaratildi: {len(Jism._tiriklar)} jism")
for j in sorted(Jism._tiriklar, key=lambda x: x.id):
    print(f"    {j}  impuls={j.impuls}, Ek={j.kinetik_energiya:.2f}")

del j3
import gc; gc.collect()
print(f"\n  del j3 → {len(Jism._tiriklar)} jism   ⚠️ tozalanmadi!")
print(f"     Sabab: tsikl o'zgaruvchisi j hali ham oxirgi obyektga —")
print(f"     aynan j3 ga — ishora qiladi (tsikldan keyin ham yashaydi).")

del j
gc.collect()
print(f"\n  del j  → {len(Jism._tiriklar)} jism   ✅ WeakSet tozalandi")

print(f"\n  ⚠️ __slots__ ga '__weakref__' qo'shilmasa:")
print(f"     Jism.__slots__ = {Jism.__slots__}")
print(f"     → weakref.WeakSet ishlamasdi")


print("\n\n5. Simulyatsiya:\n")

import random
random.seed(7)

d = Dunyo()
for _ in range(50_000):
    d.qosh(Jism(
        joy=Vektor(random.uniform(-50, 50), random.uniform(10, 100)),
        tezlik=Vektor(random.uniform(-10, 10), random.uniform(-5, 5)),
        massa=random.uniform(0.5, 5.0),
    ))

print(f"  {len(d):,} jism\n")
print(f"  {'Qadam':<8} {'Vaqt':<10} {'Energiya':<16} {'Eng baland':<12} "
      f"{'Eng tez'}")
print("  " + "─" * 58)

for qadam in range(1, 6):
    b = time.perf_counter()
    d.yangila(0.05)
    vaqt = time.perf_counter() - b
    s = d.statistika()
    print(f"  {qadam:<8} {vaqt:>7.3f}s   {s['energiya']:>14,.0f} "
          f"{s['eng_baland']:>11.2f} {s['eng_tez']:>10.2f}")


print("\n\n6. Slots tahlili:\n")


def barcha_slotlar(Sinf) -> list[str]:
    natija = []
    for S in reversed(Sinf.__mro__):
        sl = getattr(S, "__slots__", ())
        if isinstance(sl, str):
            sl = (sl,)
        natija.extend(s for s in sl if s not in natija)
    return natija


print(f"  {'Sinf':<12} {'__slots__':<44} {'__dict__'}")
print("  " + "─" * 68)
for Sinf in (Vektor, Jism, Dunyo):
    sl = str(barcha_slotlar(Sinf))
    o = (Vektor(1, 2) if Sinf is Vektor else
         Jism(Vektor(0, 0)) if Sinf is Jism else Dunyo())
    d_bor = "✅" if hasattr(o, "__dict__") else "❌"
    print(f"  {Sinf.__name__:<12} {sl[:42]:<44} {d_bor}")

print(f"""

  ⭐ BU LOYIHADA __slots__ QARORLARI:

     Vektor:
       __slots__ = ("x", "y")
       ⭐ 300 000+ nusxa → ~19% xotira tejaldi (yuqoridagi o'lchov)
       ⚠️ cached_property o'rniga @property
       ⚠️ __weakref__ kerak emas (kuzatilmaydi)

     Jism:
       __slots__ = (..., "__weakref__")
       ⭐ 50 000+ nusxa
       ⭐ __weakref__ QO'SHILDI — WeakSet kuzatuvi uchun

     Dunyo:
       __slots__ = ("_jismlar", "tortishish", "_qadam")
       ⚠️ Nusxa 1-2 ta — tejash uchun emas
       ⭐ XATO NOMLARDAN himoya uchun

  ⭐ QO'SHIMCHA OPTIMIZATSIYA:
     __iadd__ / __imul__ — joyida o'zgartirish
     → tsiklda yangi obyekt yaratilmaydi
     → {t_yangi / t_joyida:.1f}× tezroq
""")

Natijaning muhim qismi:

text
2. ⭐ Xotira:

  300,000 vektor:
    Oddiy sinf: 36.8 MB
    __slots__:  29.9 MB
    ⭐ Tejash:   6.9 MB (19%)

  Bitta obyekt:
    VektorOddiy: ~129 bayt (ro'yxat ko'rsatkichi bilan)
    Vektor:      ~105 bayt

3. Tezlik: += vs +

  500,000 qo'shish:
    a = a + d  (yangi):  0.343s
    b += d     (joyida): 0.175s
    ⭐ 2.0× tezroq

4. ⭐ __weakref__ bilan kuzatuv:

  Yaratildi: 3 jism
  del j  → 2 jism   ✅ WeakSet tozalandi

Nima ko'rsatdi: 2.3, 2.6, 2.7-bo'limlar.


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

Noto'g'ri fikr To'g'risi
"__slots__ har doim tejaydi" 3.11+ da farq kamaydi; kam obyektda sezilmaydi
"__slots__ obyektni o'zgarmas qiladi" Mavjud slotga yozish mumkin
"Avlodda __slots__ shart emas" __dict__ qaytadi, tejash yo'qoladi
"__slots__ ni takrorlash zararsiz" Xotira ikki marta egallanadi
"pickle ishlamaydi" Protokol 2+ da ishlaydi
"__slots__ — tezlik uchun" Xotira uchun; 3.11+ da tezlik farqi deyarli yo'q
"Ko'p merosda ishlaydi" Faqat bittasida bo'sh bo'lmagan
"__slots__ — optimizatsiya" Atributlarni cheklash ham foydali

6. Keng tarqalgan xatolar va yechimlari

1. Avlodda __slots__ unutish

python
class B(A):
    pass                        # ⚠️ __dict__ qaytadi
    __slots__ = ()              # ✅

2. Takrorlash

python
class B(A):
    __slots__ = ("x", "y")      # ⚠️ A da ham bor
    __slots__ = ("y",)          # ✅

3. __weakref__ unutish

python
__slots__ = ("x",)                          # ⚠️ weakref ishlamaydi
__slots__ = ("x", "__weakref__")            # ✅
@dataclass(slots=True, weakref_slot=True)   # ✅ 3.11+

4. cached_property bilan

python
__slots__ = ("x",)
@cached_property                # ❌ TypeError
@property                       # ✅ yoki
__slots__ = ("x", "__dict__")   # ✅ (lekin tejash yo'q)

5. Sinf atributi konflikti

python
__slots__ = ("x",)
x = 5                           # ❌ ValueError
                                # ✅ __init__ da o'rnating

6. Ko'p meros

python
class C(A_slots, B_slots): ...  # ❌ TypeError
class Mixin: __slots__ = ()     # ✅ mixin larda bo'sh

7. __dict__ bo'lgan ota-sinf

python
class A: pass                   # __dict__ bor
class B(A): __slots__ = ("x",)  # ⚠️ foydasiz

8. Erta optimizatsiya

python
class Sozlama:                  # 1 nusxa
    __slots__ = (...)           # ⚠️ 100 bayt tejaldi, cheklovlar qoldi

7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 8.2-dars (o'tilgan): __dict__ va atribut qidiruvi
  • 8.15-dars: deskriptorlar — member_descriptor
  • 8.17-dars: @dataclass(slots=True)
  • 8.5-dars: xotira solishtiruvi
  • sys.getsizeof, tracemalloc — o'lchash
  • NumPy, pandas: massiv asosidagi muqobil
  • CPython: Objects/typeobject.c, type_new_slots

8. Eng yaxshi amaliyotlar

  1. Avval o'lchang. tracemalloc yoki memory_profiler.

  2. 100 000+ obyekt bo'lsagina. Kamroq bo'lsa — sezilmaydi.

  3. Har avlodda __slots__ yozing. Bo'sh bo'lsa ham: ().

  4. Takrorlamang. Faqat yangi nomlar.

  5. __weakref__ ni o'ylang. Kuzatuv kerak bo'lsa qo'shing.

  6. Mixin larda __slots__ = (). Ko'p meros ishlashi uchun.

  7. dataclass(slots=True) ishlating. Qo'lda yozishdan oson.

  8. Taxmin qilmang — o'lchang. tracemalloc bilan; NamedTuple va tuple ham slotli sinfdan ko'proq xotira olishi mumkin.


9. Amaliy topshiriq

Vazifa 1: Natijani bashorat qiling

python
1.  class A: __slots__ = ("x",)
    a = A(); a.x = 1
    try: a.y = 2
    except AttributeError: print("xato")
2.  class A: __slots__ = ("x",)
    print(hasattr(A(), "__dict__"))
3.  class A: __slots__ = ("x",)
    class B(A): pass
    print(hasattr(B(), "__dict__"))
4.  class A: __slots__ = ("x",)
    class B(A): __slots__ = ()
    print(hasattr(B(), "__dict__"))
5.  class A: __slots__ = ("x",)
    print(type(A.x).__name__)
6.  class A:
        __slots__ = ("x",)
        x = 5
    # ?
7.  import weakref
    class A: __slots__ = ("x",)
    try: weakref.ref(A())
    except TypeError: print("xato")
8.  class A: __slots__ = "x"
    a = A(); a.x = 1
    print(a.x)
9.  class A: __slots__ = ("x",)
    class B: __slots__ = ("y",)
    try:
        class C(A, B): pass
    except TypeError: print("xato")
10. class A: __slots__ = ("x", "__dict__")
    a = A(); a.x = 1; a.y = 2
    print(a.__dict__)
11. from dataclasses import dataclass
    @dataclass(slots=True)
    class A: x: int
    print(hasattr(A(1), "__dict__"))
12. class A: __slots__ = ("x",)
    print(A.__slots__, sorted(A.__dict__.keys())[:2])
Javoblar
  1. xato
  2. False
  3. True — __slots__ yo'q
  4. False —
  5. member_descriptor
  6. ValueError: 'x' in __slots__ conflicts with class variable
  7. xato
  8. 1 — satr ham ishlaydi
  9. xato — layout conflict
  10. {'y': 2} — x slotda
  11. False
  12. ('x',) ['__doc__', '__firstlineno__'] (3.13+; oldin ['__doc__', '__module__']) — sinf lug'atida __slots__, x (member_descriptor) va dunder'lar bor

Vazifa 2: Xatolarni tuzating

python
1.  class B(A):        # A da __slots__ bor
        pass
2.  class B(A):
        __slots__ = ("x", "y", "z")   # A: __slots__ = ("x", "y")
3.  class A:
        __slots__ = ("x",)
        @cached_property
        def y(self): ...
4.  class A: __slots__ = ("x",)
    weakref.ref(A())
5.  class Mixin: pass
    class C(Mixin, SlotliSinf): ...
6.  class A:
        __slots__ = ("x",)
        x = 0
7.  class Sozlama:     # 1 nusxa
        __slots__ = (...)
8.  class A(Oddiy):    # Oddiy da __slots__ yo'q
        __slots__ = ("x",)
Javoblar
python
1.  __slots__ = ()  qo'shing
2.  __slots__ = ("z",)   — faqat yangi
3.  @property  yoki __slots__ = ("x", "__dict__")
4.  __slots__ = ("x", "__weakref__")
5.  class Mixin: __slots__ = ()
6.  x = 0 ni olib tashlang, __init__ da o'rnating
7.  __slots__ kerak emas (yoki faqat cheklash uchun)
8.  Oddiy ga ham __slots__ qo'shing (aks holda foydasiz)

Vazifa 3: Piksel sinfi

Yozing:

  1. __slots__ = ("r", "g", "b", "a")
  2. Arifmetika: aralashtirish, yorug'lik
  3. __iadd__ bilan joyida o'zgartirish
  4. 1 million piksel xotirasini o'lchang
  5. NamedTuple bilan solishtiring
  6. array moduli bilan ham solishtiring

Vazifa 4: __slots__ tekshiruvchisi

Vosita yozing:

  1. __slots__ bor sinflarni topsin
  2. Avlodda __slots__ yo'qligini
  3. Takrorlangan slotlarni
  4. __dict__ bor-yo'qligini (tejash yo'qmi)
  5. __weakref__ kerakligini (weakref ishlatilsa)
  6. Tejash miqdorini hisoblasin

Vazifa 5: Aralash yondashuv

  1. Asosiy atributlar __slots__ da
  2. Kamdan-kam ishlatiladiganlar __dict__ da
  3. Xotira farqini o'lchang
  4. Qaysi atributlar tez-tez ishlatilishini aniqlang
  5. __getattr__ bilan kechiktirilgan yuklash
  6. Uch variantni solishtiring

Vazifa 6: Migratsiya

Mavjud sinfni __slots__ ga o'tkazing:

  1. Barcha atributlarni toping (__init__ va boshqa metodlar)
  2. cached_property larni property ga
  3. weakref ishlatilishini tekshiring
  4. Avlodlarni yangilang
  5. pickle/copy testlarini o'tkazing
  6. Xotira farqini hujjatlang

Vazifa 7: O'ylash

Nega __slots__ sukut bo'yicha yoqilmagan — agar u xotira tejasa va tezroq bo'lsa?

Javob

Chunki Pythonning dinamikligi — uning asosiy kuchi, va __slots__ uni cheklaydi.

1. Dinamik atributlar — Pythonning asosiy xususiyati

python
class A: pass

a = A()
a.istalgan = 1                  # ✅ ishlaydi
a.yana = "matn"
a.__dict__                      # {'istalgan': 1, 'yana': 'matn'}

Bu — xususiyat, kamchilik emas. Undan foydalanadigan narsalar:

a) Monkey patching:

python
import kutubxona
kutubxona.Sinf.tuzatilgan_metod = mening_versiyam
obj.vaqtinchalik_bayroq = True

b) Kutubxonalar:

python
# Flask
app.config["KALIT"] = "qiymat"
g.foydalanuvchi = current_user      # ⭐ dinamik kontekst

# pytest
def test_a(request):
    request.mening_malumotim = ...

# unittest.mock
mock.istalgan_atribut.istalgan_metod()

c) ORM va serializatorlar:

python
# SQLAlchemy
obj._sa_instance_state = ...        # ⭐ ichki holat

# Django
obj._state = ModelState()
obj._prefetched_objects_cache = {}

__slots__ sukut bo'yicha bo'lsa — bularning hammasi buzilardi.

2. Orqaga moslik

__slots__ Python 2.2 (2001) da qo'shildi. O'sha paytda millionlab qator kod bor edi:

python
class A: pass
a = A()
a.qoshimcha = 1                 # ⚠️ endi buzilardi

Sukut bo'yicha yoqish — butun ekotizimni buzardi.

3. Meros bilan murakkablik

python
class A:
    __slots__ = ("x",)

class B(A):
    # ⚠️ Har avlodda __slots__ yozish kerak
    # ⚠️ Takrorlash — xotira behuda
    # ⚠️ Ko'p meros — layout conflict

Sukut bo'yicha bo'lsa, har sinf yozgan dasturchi bu qoidalarni bilishi kerak bo'lardi.

4. Foyda har doim ham yo'q

Python 3.11+ da "key-sharing dictionaries":

Python 3.2 dan (PEP 412):
  Bir sinfning barcha nusxalari KALITLARNI ulashadi

  1000 ta Nuqta obyekti:
    Eski:   1000 × (kalitlar + qiymatlar)
    Yangi:  1 × kalitlar + 1000 × qiymatlar

Natijada __dict__ ustama xarajati ancha kamaydi, va __slots__ foydasi ham.

O'lchov (Python 3.14, 2 atribut, tracemalloc):

__dict__ __slots__ Tejash
Bitta obyekt ~88 bayt ~48 bayt ~45%

Python 3.3 gacha (PEP 412 dan oldin) har nusxaning to'liq alohida lug'ati bo'lgani uchun farq bundan ancha katta edi. 3.11+ dagi "inline values" uni yana kamaytirdi — lekin ko'p obyektda baribir sezilarli.

5. Cheklovlar zanjiri

__slots__ sukut bo'yicha yoqilsa, quyidagilar ishlamasdi:

python
# 1. cached_property
@cached_property
def x(self): ...                # ❌

# 2. weakref
weakref.ref(obj)                # ❌

# 3. Dinamik atributlar
obj.yangi = 1                   # ❌

# 4. Ko'p meros
class C(A, B): ...              # ❌ layout conflict

# 5. Ba'zi kutubxonalar
pickle (eski protokol)          # ❌
copy (maxsus holatlarda)        # ⚠️

Har biri uchun maxsus yechim kerak bo'lardi.

6. Boshqa tillar

Til Yondashuv
Python __dict__ sukut, __slots__ ixtiyoriy
JavaScript Dinamik, lekin "hidden classes" (V8)
Ruby Dinamik (instance_variable_set)
Java Statik maydonlar (majburiy)
C# Statik + dynamic (ixtiyoriy)
Go, Rust Statik struct

JavaScript eng qiziq:

javascript
class Nuqta {
    constructor(x, y) { this.x = x; this.y = y; }
}

V8 dvigateli avtomatik "hidden class" (shape) yaratadi — bu __slots__ ga o'xshash optimizatsiya, lekin dasturchi ko'rmaydi.

Lekin dinamik atribut qo'shsangiz:

javascript
const p = new Nuqta(1, 2);
p.z = 3;                        // ⚠️ yangi hidden class — sekinlashadi

V8 "shape transition" qiladi — ishlaydi, lekin sekinroq.

7. Nega Python bunday qilmadi

CPython da "hidden classes" ni amalga oshirish mumkin edi, lekin:

  1. Murakkab — V8 da bu minglab qator C++ kod
  2. CPython sodda bo'lishi kerak — o'qish va hissa qo'shish oson
  3. Boshqa optimizatsiyalar muhimroq — 3.11 dagi "Specializing Adaptive Interpreter"

Lekin Python qisman shu yo'ldan bordi:

PEP 412 3.3-bob — "Key-Sharing Dictionary":

Bir sinfning nusxalari kalit jadvalini ULASHADI
→ V8 hidden class g'oyasining soddalashtirilgan versiyasi

3.11 — "Lazy __dict__ creation":

__dict__ FAQAT kerak bo'lganda yaratiladi
→ atributsiz obyektlar kam joy egallaydi

8. __slots__ — ixtiyoriy vosita

Bu — Pythonning odatiy naqshi:

Muammo Sukut Ixtiyoriy optimizatsiya
Xotira __dict__ __slots__
Tur xavfsizligi Duck typing mypy
Tezlik Sof Python C kengaytma, Cython
Validatsiya Yo'q pydantic
Ma'lumot sinfi Qo'lda dataclass

Siz to'laysiz faqat kerak bo'lganda.

9. Amaliy tavsiya

python
# ⭐ 1. Sukut — __slots__ SIZ
class Xizmat:
    def __init__(self, nom): self.nom = nom

# ⭐ 2. O'lchang
import tracemalloc
tracemalloc.start()
obyektlar = [Xizmat(i) for i in range(1_000_000)]
print(tracemalloc.get_traced_memory()[0] / 1_048_576, "MB")

# ⭐ 3. Muammo bo'lsa — __slots__
@dataclass(slots=True)
class Xizmat:
    nom: str

# ⭐ 4. Yoki butunlay boshqa yondashuv
import numpy as np
nomlar = np.array([...])        # massiv — eng kam xotira

10. Xulosa

__slots__ sukut bo'yicha yoqilmagan, chekki:

  1. Dinamiklik — Pythonning asosiy kuchi
  2. Orqaga moslik — millionlab qator kod buzilardi
  3. Cheklovlar — cached_property, weakref, ko'p meros
  4. Meros murakkabligi — har avlodda yozish kerak
  5. Foyda kamaydi — key-sharing dict (3.3+)

Va bu — Pythonning umumiy falsafasi: sukut bo'yicha moslashuvchan, kerak bo'lganda optimallashtiriladi.

Donald Knuth:

"Premature optimization is the root of all evil." (Erta optimizatsiya — barcha yomonlikning ildizi.)

__slots__ — o'lchagandan keyin qo'llaniladigan vosita, "har ehtimolga qarshi" emas.

Nimani mustahkamlaydi: 2.3, 2.5, 2.7-bo'limlar.


Xulosa

Bu darsda __slots__ ni o'rgandik.

Eng muhim uch fikr:

  1. __slots__ lug'atni massiv bilan almashtiradi. Har atribut uchun member_descriptor (data deskriptor) yaratiladi va qiymat obyektning belgilangan o'rnida saqlanadi. Natija — Python 3.14 da har obyektga ~20–50% kam xotira (atributlar qancha kam bo'lsa, ulush shuncha katta). Tezlik farqi zamonaviy Pythonda deyarli yo'q. Foyda faqat 100 000 dan ko'p obyekt bo'lganda seziladi, va uni tracemalloc bilan o'lchang — sys.getsizeof(obj.__dict__) 3.11+ da lug'atni o'zi yaratib, natijani buzadi.

  2. Har avlodda __slots__ yozish shart. Avlodda __slots__ yo'q bo'lsa — __dict__ qaytadi va butun tejash yo'qoladi. Bo'sh bo'lsa ham __slots__ = () yozing. Ota-sinfdagi nomlarni takrorlamang — ular ikki marta joy egallaydi. Mixin larda __slots__ = () — ko'p meros ishlashi uchun.

  3. Beshta cheklovni ongli qabul qiling. Yangi atribut, __dict__, __weakref__, cached_property va ko'p meros — hammasi cheklanadi. __weakref__ ni __slots__ ga qo'shish mumkin, cached_property o'rniga @property ishlatiladi. Eng oson yo'l — @dataclass(slots=True). NamedTuple esa o'zgarmaslik uchun yaxshi, lekin xotirada slotli sinfdan ko'proq joy oladi.

Keyingi dars — 8-qismning yakuni: kompozitsiya va meros orasidagi tanlov, va butun OOP bilimini bir arxitekturaviy qarorga bog'lash.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
8.19-dars: slots — IlmHamroh