IlmHamroh
Python kursi/Ilgor til imkoniyatlari10/17-dars28 daqiqa
Mundarija (22)

10.10-dars: Klass dekoratori

10-QISM — ILG'OR TIL IMKONIYATLARI · 10-dars


1. Kirish va motivatsiya

Internetda tez-tez uchraydigan "singleton" dekoratori:

python
def singleton(klass):
    nusxalar = {}
    def ol(*args, **kwargs):
        if klass not in nusxalar:
            nusxalar[klass] = klass(*args, **kwargs)
        return nusxalar[klass]
    return ol

@singleton
class Sozlama:
    ...

Sozlama() ikki marta chaqirilsa, bitta obyekt qaytadi. Ishlaydi. Keyin jamoadagi kimdir yozadi:

python
isinstance(obj, Sozlama)        # TypeError: isinstance() arg 2 must be a type, ...
class TestSozlama(Sozlama): ... # TypeError: function() argument 'code' must be code, not str

Chunki Sozlama endi klass emas — funksiya. Dekorator klassni boshqa narsa bilan almashtirib, klassning barcha va'dalarini buzdi: isinstance, meros, klass metodlari, Sozlama.__name__.

Klass dekoratori ham funksiya dekoratori kabi K = dek(K) qiladi. Lekin klassning "shartnomasi" funksiyanikidan ancha keng: meros, tur tekshiruvi, deskriptorlar, __set_name__. Shuning uchun klass dekoratorining eng xavfsiz ishi — klassni o'zini qaytarish.

Bu darsda:

  • Klass yaratilishida dekorator qaysi lahzada ishlaydi
  • Uch strategiya: ro'yxatga olish, joyida o'zgartirish, almashtirish
  • Klassga metod qo'shish va barcha metodlarni o'rash — tuzoqlari bilan
  • Dekoratorlar meros bo'lmaydi — __init_subclass__ bilan solishtirish
  • dataclass(slots=True) yangi klass qaytaradi — tartibga ta'siri
  • Klass funksiya dekoratori sifatida va metodlardagi self tuzog'i

2. Nazariya — chuqur tushuntirish

2.1. Ochilishi va lahzasi

python
@A
@B
class K:
    ...
python
class K:
    ...
K = A(B(K))

Klass yaratilish tartibi:

  1. Dekorator ifodalari (A, B) baholanadi
  2. Klass tanasi bajariladi — atributlar va metodlar lug'atga yig'iladi
  3. Metaklass (odatda type) klass obyektini yaratadi
  4. Tanadagi har bir obyekt uchun __set_name__(klass, nom) chaqiriladi
  5. Ota klassning __init_subclass__ chaqiriladi
  6. Dekoratorlar pastdan yuqoriga qo'llanadi: B, keyin A
  7. Natija K nomiga bog'lanadi

Demak, dekorator tayyor klassni oladi: metodlari bor, merosi o'rnatilgan, __set_name__ allaqachon ishlagan.

2.2. Uch strategiya

Strategiya Qaytaradi Misol Xavf
Ro'yxatga olish O'sha klass Plagin, model, serializer ro'yxati Juda kam
Joyida o'zgartirish O'sha klass @dataclass, @total_ordering, @enum.unique O'rtacha — mavjud narsani buzish mumkin
Almashtirish Boshqa obyekt @singleton (funksiya), o'rovchi klass Yuqori

Almashtirishda nima buziladi:

Qaytarilgan narsa Buziladi
Funksiya isinstance, meros, klass atributlari, classmethod lar
Merosxo'r klass __name__/__qualname__ (tuzatish kerak), pickle, MRO ga qo'shimcha qatlam
Umuman boshqa klass Oldin olingan havolalar eski klassga ishora qiladi

2.3. Klassga metod qo'shish

python
def avto_repr(klass):
    def __repr__(self):
        maydonlar = ", ".join(f"{k}={v!r}" for k, v in vars(self).items())
        return f"{type(self).__name__}({maydonlar})"
    if "__repr__" not in klass.__dict__:          # ⭐ foydalanuvchi o'zi yozganini buzmaymiz
        __repr__.__qualname__ = f"{klass.__qualname__}.__repr__"
        klass.__repr__ = __repr__
    return klass

Uch nozik joy:

1. hasattr emas, klass.__dict__. Har klass object dan __repr__, __eq__, __hash__ meros oladi — hasattr(klass, "__repr__") har doim True. Klassning o'zi aniqlaganini bilish uchun "__repr__" in klass.__dict__.

2. __qualname__. Dekorator ichida yaratilgan funksiyaning __qualname__ — avto_repr.<locals>.__repr__. Traceback va repr da chalkashlik bo'lmasligi uchun tuzating (dataclass ham shunday qiladi).

3. __set_name__ chaqirilmaydi. Klass yaratilgandan keyin qo'shilgan deskriptor __set_name__ ni olmaydi — u 4-qadamda, dekoratordan oldin bo'lgan. Kerak bo'lsa, qo'lda chaqiring:

python
deskriptor = Maydon()
klass.narx = deskriptor
deskriptor.__set_name__(klass, "narx")        # ⭐

2.4. Barcha metodlarni o'rash

python
def jurnal_bilan(klass):
    for nom, qiymat in list(vars(klass).items()):
        if nom.startswith("__"):
            continue
        if isinstance(qiymat, (staticmethod, classmethod)):
            setattr(klass, nom, type(qiymat)(jurnal(qiymat.__func__)))
        elif isinstance(qiymat, property):
            continue                               # yoki fget/fset ni alohida o'rang
        elif callable(qiymat):
            setattr(klass, nom, jurnal(qiymat))
    return klass

Tuzoqlar:

  • vars(klass) da staticmethod va classmethod — o'rovchi obyektlar, funksiya emas. Ularni to'g'ridan-to'g'ri o'rab bo'lmaydi (10.8-dars) — ichidagi __func__ ni o'rab, qayta o'rash kerak
  • property — chaqiriladigan emas, lekin ichida funksiyalar bor
  • Meros qilingan metodlar vars(klass) da yo'q — o'ralmaydi
  • Iteratsiya paytida lug'atni o'zgartirmaslik uchun list(...)
  • Dunder metodlarni o'rash cheksiz rekursiyaga olib kelishi mumkin (__repr__ ichida jurnal repr(self) chaqirsa)

2.5. Dekoratorlar meros bo'lmaydi

python
@royxatga_ol
class Plagin: ...

class CSVPlagin(Plagin): ...       # ❌ ro'yxatda yo'q

Dekorator faqat o'zi yozilgan klassga bir marta qo'llanadi. Merosxo'r klass dekoratorning natijasini (qo'shilgan metodlarni) meros oladi, lekin dekoratorning o'zi qayta ishlamaydi.

Merosxo'rlarga ham avtomatik qo'llanishi kerak bo'lsa — __init_subclass__ (PEP 487, Python 3.6):

python
class Plagin:
    royxat = {}

    def __init_subclass__(cls, /, nom=None, **kwargs):
        super().__init_subclass__(**kwargs)
        Plagin.royxat[nom or cls.__name__.lower()] = cls

class CSVPlagin(Plagin, nom="csv"): ...      # ✅ avtomatik
class TSVPlagin(CSVPlagin): ...              # ✅ bu ham
Klass dekoratori __init_subclass__ Metaklass
Qo'llanadi Faqat bezatilgan klassga Barcha merosxo'rlarga (o'ziga emas) Klassning o'ziga va merosxo'rlarga
Ko'rinishi @ bilan aniq Bazada yashirin Bazada yashirin
Klassni almashtira oladimi Ha Yo'q Ha
Moslik muammolari Yo'q Kam Metaklass to'qnashuvi
Qachon Tanlab qo'shiladigan xususiyat Ierarxiya qoidasi Juda kam — klass yaratilishini tubdan o'zgartirish

2.6. dataclass(slots=True) — yangi klass

@dataclass odatda klassni joyida o'zgartiradi. Lekin slots=True bilan u yangi klass yaratadi: __slots__ ni klass yaratilgandan keyin qo'shib bo'lmaydi.

python
@dataclass(slots=True)
@royxatga_ol            # ❌ ro'yxatga eski klass tushadi
class Nuqta:
    x: int
    y: int

@royxatga_ol            # ✅ yakuniy klass
@dataclass(slots=True)
class Nuqta: ...

Eski klass bilan yaratilgan obyekt isinstance(obj, Nuqta) da False beradi. 10.8-darsdagi qoida klasslarga ham tegishli: ro'yxatga oluvchi — eng yuqorida.

2.7. Klass — funksiya dekoratori sifatida

Boshqa yo'nalish: klassning o'zi dekorator bo'ladi.

python
class Sanagich:
    def __init__(self, funksiya):
        functools.update_wrapper(self, funksiya)   # __name__, __doc__, __wrapped__
        self.funksiya = funksiya
        self.soni = 0

    def __call__(self, *args, **kwargs):
        self.soni += 1
        return self.funksiya(*args, **kwargs)

@Sanagich
def hisobla(x): ...

hisobla(1); hisobla(2)
hisobla.soni                    # 2 — holat oddiy atribut

hisobla endi Sanagich nusxasi. Oddiy funksiyalar uchun zo'r — holat ochiq. Lekin metodlarda buziladi:

python
class Hisob:
    @Sanagich
    def qoldiq(self): ...

Hisob().qoldiq()                # ❌ TypeError: Hisob.qoldiq() missing 1 required positional argument: 'self'

Sababi: oddiy funksiya deskriptor — obj.metod ga murojaat qilinganda __get__ orqali self bog'langan metodga aylanadi. Sanagich nusxasida __get__ yo'q — u klass atributi sifatida qaytadi, self bog'lanmaydi.

Yechim — __get__ qo'shish (deskriptor protokoli — 10.17-dars):

python
def __get__(self, obj, egasi=None):
    if obj is None:
        return self
    return types.MethodType(self, obj)       # obj ni birinchi argument sifatida bog'laydi

Bunda soni barcha nusxalar uchun umumiy — dekorator klass darajasida bitta.

2.8. Standart kutubxonadagi klass dekoratorlari

Dekorator Nima qiladi Strategiya
dataclasses.dataclass __init__, __repr__, __eq__... qo'shadi Joyida (slots=True — yangi klass)
functools.total_ordering Bitta taqqoslashdan qolganlarini yasaydi Joyida
enum.unique Takroriy qiymatlarni taqiqlaydi Tekshiruv, o'sha klass
typing.runtime_checkable Protokolni isinstance da ishlatish Joyida
typing.final Tur tekshiruvchiga "meros olinmasin" Belgi
unittest.mock.patch(...) test bilan boshlanadigan har metodni o'raydi Joyida

3. Tez ma'lumotnoma

Ochilishi va lahza

@A @B class K   →   K = A(B(K))
tana → type() → __set_name__ → __init_subclass__ → B → A → nom

Shablonlar

python
def royxatga_ol(klass):            # ro'yxat
    ROYXAT[klass.__name__] = klass
    return klass

def qosh(klass):                   # joyida o'zgartirish
    if "metod" not in klass.__dict__:
        def metod(self): ...
        metod.__qualname__ = f"{klass.__qualname__}.metod"
        klass.metod = metod
    return klass

Qoidalar

hasattr(klass, "__repr__")      → har doim True (object dan)
"__repr__" in klass.__dict__    → klassning o'zi aniqlaganmi
Keyin qo'shilgan deskriptor      → __set_name__ ni qo'lda chaqiring
vars(klass)                     → faqat o'z atributlari, staticmethod/classmethod o'ralgan
Meros                           → dekorator qayta ishlamaydi → __init_subclass__
dataclass(slots=True)           → yangi klass → ro'yxatga oluvchi yuqorida
Funksiya qaytarish              → isinstance va meros buziladi

Klass funksiya dekoratori sifatida

python
class Dek:
    def __init__(self, f): functools.update_wrapper(self, f); self.f = f
    def __call__(self, *a, **k): return self.f(*a, **k)
    def __get__(self, obj, egasi=None):          # metodlar uchun
        return self if obj is None else types.MethodType(self, obj)

4. Batafsil misollar

Misol 1 — Klass yaratilishi va dekorator lahzasi

python
"""Tana, __set_name__, __init_subclass__ va dekoratorlar tartibi; meros; klassni almashtirish oqibatlari."""


class Maydon:
    def __set_name__(self, egasi, nom):
        print(f"  [4] __set_name__: {egasi.__name__}.{nom}")
        self.nom = nom


class Baza:
    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        print(f"  [5] Baza.__init_subclass__: {cls.__name__}")


def dekorator(yorliq):
    print(f"  [1] dekorator ifodasi: {yorliq}")

    def qolla(klass):
        print(f"  [6] {yorliq} qo'llandi: {klass.__name__}, "
              f"metodlari: {[k for k in vars(klass) if not k.startswith('__')]}")
        return klass

    return qolla


print("=== 1. Tartib ===")


@dekorator("A")
@dekorator("B")
class Mahsulot(Baza):
    print("  [2] klass tanasi bajarilmoqda")
    narx = Maydon()

    def chegirma(self):
        return 0


print(f"  [7] Mahsulot nomiga bog'landi: {Mahsulot}")

print("\n=== 2. ⭐ Dekorator meros bo'lmaydi, __init_subclass__ esa bo'ladi ===")
dekorator_royxati = []
subclass_royxati = []


def royxatga_ol(klass):
    dekorator_royxati.append(klass.__name__)
    return klass


class Plagin:
    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        subclass_royxati.append(cls.__name__)


@royxatga_ol
class CSV(Plagin):
    pass


class TSV(CSV):
    pass


@royxatga_ol
class JSON(Plagin):
    pass


print(f"  dekorator ro'yxati:        {dekorator_royxati}   ⚠️ TSV yo'q")
print(f"  __init_subclass__ ro'yxati: {subclass_royxati}")

print("\n=== 3. ⚠️ Klassni funksiya bilan almashtirish ===")


def singleton(klass):
    nusxalar = {}

    def ol(*args, **kwargs):
        if klass not in nusxalar:
            nusxalar[klass] = klass(*args, **kwargs)
        return nusxalar[klass]

    return ol


@singleton
class Sozlama:
    til = "uz"

    @classmethod
    def standart(cls):
        return cls()


s1, s2 = Sozlama(), Sozlama()
print(f"  bitta obyekt: {s1 is s2}, Sozlama turi: {type(Sozlama).__name__}")


def meros_ol():
    class TestSozlama(Sozlama):
        pass


tekshiruvlar = [
    ("isinstance", lambda: isinstance(s1, Sozlama)),
    ("meros", meros_ol),
    ("klass atributi", lambda: Sozlama.til),
    ("classmethod", lambda: Sozlama.standart()),
]
for nom, amal in tekshiruvlar:
    try:
        amal()
        print(f"  {nom}: ishladi")
    except (TypeError, AttributeError) as e:
        print(f"  ❌ {nom}: {type(e).__name__}: {e}")

print("\n=== 4. ✅ Singleton klassni o'zgartirmay — __new__ orqali ===")


def yagona(klass):
    asl_new = klass.__new__

    def __new__(cls, *args, **kwargs):
        if "_yagona" not in cls.__dict__:
            cls._yagona = asl_new(cls) if asl_new is object.__new__ else asl_new(cls, *args, **kwargs)
        return cls._yagona

    klass.__new__ = __new__
    return klass


@yagona
class Sozlama2:
    til = "uz"

    @classmethod
    def standart(cls):
        return cls()


a, b = Sozlama2(), Sozlama2.standart()
print(f"  bitta obyekt: {a is b}, turi: {type(Sozlama2).__name__}, isinstance: {isinstance(a, Sozlama2)}")


class TestSozlama2(Sozlama2):
    pass


print(f"  meros ishlaydi: {TestSozlama2.__mro__[1].__name__}, merosxo'rning o'z nusxasi: {TestSozlama2() is not a}")
print("  (__init__ har chaqiruvda qayta ishlaydi — singleton'ning mashhur nozik joyi, 10.16-dars)")

Natijaning muhim qismi:

text
=== 1. Tartib ===
  [1] dekorator ifodasi: A
  [1] dekorator ifodasi: B
  [2] klass tanasi bajarilmoqda
  [4] __set_name__: Mahsulot.narx
  [5] Baza.__init_subclass__: Mahsulot
  [6] B qo'llandi: Mahsulot, metodlari: ['narx', 'chegirma']
  [6] A qo'llandi: Mahsulot, metodlari: ['narx', 'chegirma']
  [7] Mahsulot nomiga bog'landi: <class '__main__.Mahsulot'>

=== 2. ⭐ Dekorator meros bo'lmaydi, __init_subclass__ esa bo'ladi ===
  dekorator ro'yxati:        ['CSV', 'JSON']   ⚠️ TSV yo'q
  __init_subclass__ ro'yxati: ['CSV', 'TSV', 'JSON']

=== 3. ⚠️ Klassni funksiya bilan almashtirish ===
  bitta obyekt: True, Sozlama turi: function
  ❌ isinstance: TypeError: isinstance() arg 2 must be a type, a tuple of types, or a union
  ❌ meros: TypeError: function() argument 'code' must be code, not str
  ❌ klass atributi: AttributeError: 'function' object has no attribute 'til'
  ❌ classmethod: AttributeError: 'function' object has no attribute 'standart'

=== 4. ✅ Singleton klassni o'zgartirmay — __new__ orqali ===
  bitta obyekt: True, turi: type, isinstance: True
  meros ishlaydi: Sozlama2, merosxo'rning o'z nusxasi: True
  (__init__ har chaqiruvda qayta ishlaydi — singleton'ning mashhur nozik joyi, 10.16-dars)

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

Misol 2 — Klassni joyida o'zgartirish

python
"""avto_repr (hasattr tuzog'i), taqqoslanadigan (argumentli), barcha metodlarni o'rash, __set_name__ tuzog'i."""

import functools


print("=== 1. ⚠️ hasattr har doim True ===")


class Bosh:
    pass


print(f"  hasattr(Bosh, '__repr__'): {hasattr(Bosh, '__repr__')}")
print(f"  '__repr__' in Bosh.__dict__: {'__repr__' in Bosh.__dict__}")


def avto_repr(klass):
    def __repr__(self):
        maydonlar = ", ".join(f"{k}={v!r}" for k, v in vars(self).items())
        return f"{type(self).__name__}({maydonlar})"

    if "__repr__" not in klass.__dict__:
        __repr__.__qualname__ = f"{klass.__qualname__}.__repr__"
        klass.__repr__ = __repr__
    return klass


@avto_repr
class Talaba:
    def __init__(self, ism, kurs):
        self.ism, self.kurs = ism, kurs


@avto_repr
class Pul:
    def __init__(self, summa):
        self.summa = summa

    def __repr__(self):
        return f"{self.summa:,} so'm"


print(f"  {Talaba('Aziz', 2)!r}, qualname: {Talaba.__repr__.__qualname__}")
print(f"  {Pul(150000)!r}   ← o'zi yozgan __repr__ saqlandi")

print("\n=== 2. Argumentli klass dekoratori: taqqoslanadigan(kalit) ===")


def taqqoslanadigan(kalit):
    def dekorator(klass):
        def __eq__(self, boshqa):
            if type(boshqa) is not type(self):
                return NotImplemented
            return kalit(self) == kalit(boshqa)

        def __lt__(self, boshqa):
            if type(boshqa) is not type(self):
                return NotImplemented
            return kalit(self) < kalit(boshqa)

        for f in (__eq__, __lt__):
            f.__qualname__ = f"{klass.__qualname__}.{f.__name__}"
            setattr(klass, f.__name__, f)
        klass.__hash__ = lambda self: hash(kalit(self))
        return functools.total_ordering(klass)
    return dekorator


@taqqoslanadigan(kalit=lambda v: (v.katta, v.kichik, v.tuzatish))
@avto_repr
class Versiya:
    def __init__(self, matn):
        self.katta, self.kichik, self.tuzatish = map(int, matn.split("."))


versiyalar = [Versiya(v) for v in ["1.10.0", "1.2.3", "1.2.10", "0.9.9"]]
print(f"  saralangan: {[f'{v.katta}.{v.kichik}.{v.tuzatish}' for v in sorted(versiyalar)]}")
print(f"  1.2.10 >= 1.2.3: {Versiya('1.2.10') >= Versiya('1.2.3')}  (>= total_ordering yasadi)")
print(f"  to'plamda: {len({Versiya('1.0.0'), Versiya('1.0.0'), Versiya('2.0.0')})} ta")

print("\n=== 3. Barcha metodlarni o'rash ===")
chaqiruvlar = []


def jurnal(funksiya):
    def orovchi(*args, **kwargs):
        chaqiruvlar.append(funksiya.__name__)
        return funksiya(*args, **kwargs)
    return orovchi


def sodda_jurnal_bilan(klass):
    for nom, qiymat in list(vars(klass).items()):
        if not nom.startswith("__") and callable(qiymat):
            setattr(klass, nom, jurnal(qiymat))
    return klass


def jurnal_bilan(klass):
    for nom, qiymat in list(vars(klass).items()):
        if nom.startswith("__"):
            continue
        if isinstance(qiymat, (staticmethod, classmethod)):
            setattr(klass, nom, type(qiymat)(jurnal(qiymat.__func__)))
        elif isinstance(qiymat, property):
            setattr(klass, nom, property(jurnal(qiymat.fget)))
        elif callable(qiymat):
            setattr(klass, nom, jurnal(qiymat))
    return klass


class AsosiyHisob:
    def tarix(self):
        return []


def hisob_klassi(dekorator):
    class Hisob(AsosiyHisob):
        def __init__(self, balans):
            self.balans = balans

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

        @staticmethod
        def valyuta():
            return "UZS"

        @classmethod
        def bosh(cls):
            return cls(0)

        @property
        def holat(self):
            return "musbat" if self.balans >= 0 else "qarz"

    return dekorator(Hisob)


for nom, dek in [("sodda", sodda_jurnal_bilan), ("to'g'ri", jurnal_bilan)]:
    chaqiruvlar.clear()
    H = hisob_klassi(dek)
    natijalar = []
    for amal in [lambda: H(100).yech(30), lambda: H.valyuta(), lambda: H(5).valyuta(),
                 lambda: H.bosh().balans, lambda: H(1).holat, lambda: H(1).tarix()]:
        try:
            natijalar.append(amal())
        except TypeError as e:
            natijalar.append(f"TypeError")
    print(f"  {nom}: {natijalar}")
    print(f"    jurnal: {chaqiruvlar}")
print("  ⚠️ sodda: staticmethod oddiy funksiyaga o'ralib nusxadan buzildi,")
print("     classmethod va property esa callable emas — jim o'ralmay qoldi")
print("  ⚠️ ikkalasida ham tarix() yo'q — meros qilingan metod vars(klass) da emas")

print("\n=== 4. ⚠️ Keyin qo'shilgan deskriptor __set_name__ ni olmaydi ===")


class Musbat:
    def __set_name__(self, egasi, nom):
        self.nom = "_" + nom

    def __get__(self, obj, egasi=None):
        return self if obj is None else getattr(obj, self.nom)

    def __set__(self, obj, qiymat):
        if qiymat < 0:
            raise ValueError(f"{self.nom[1:]} manfiy bo'lmasin: {qiymat}")
        setattr(obj, self.nom, qiymat)


def narx_qosh(tuzat):
    def dekorator(klass):
        d = Musbat()
        klass.narx = d
        if tuzat:
            d.__set_name__(klass, "narx")
        return klass
    return dekorator


for tuzat in (False, True):
    @narx_qosh(tuzat)
    class Tovar:
        pass

    t = Tovar()
    try:
        t.narx = 5000
        print(f"  tuzat={tuzat}: narx={t.narx}")
        t.narx = -1
    except (AttributeError, ValueError) as e:
        print(f"  tuzat={tuzat}: {type(e).__name__}: {e}")

Natijaning muhim qismi:

text
=== 1. ⚠️ hasattr har doim True ===
  hasattr(Bosh, '__repr__'): True
  '__repr__' in Bosh.__dict__: False
  Talaba(ism='Aziz', kurs=2), qualname: Talaba.__repr__
  150,000 so'm   ← o'zi yozgan __repr__ saqlandi

=== 2. Argumentli klass dekoratori: taqqoslanadigan(kalit) ===
  saralangan: ['0.9.9', '1.2.3', '1.2.10', '1.10.0']
  1.2.10 >= 1.2.3: True  (>= total_ordering yasadi)
  to'plamda: 2 ta

=== 3. Barcha metodlarni o'rash ===
  sodda: [70, 'UZS', 'TypeError', 0, 'musbat', []]
    jurnal: ['yech', 'valyuta', 'valyuta']
  to'g'ri: [70, 'UZS', 'UZS', 0, 'musbat', []]
    jurnal: ['yech', 'valyuta', 'valyuta', 'bosh', 'holat']
  ⚠️ sodda: staticmethod oddiy funksiyaga o'ralib nusxadan buzildi,
     classmethod va property esa callable emas — jim o'ralmay qoldi
  ⚠️ ikkalasida ham tarix() yo'q — meros qilingan metod vars(klass) da emas

=== 4. ⚠️ Keyin qo'shilgan deskriptor __set_name__ ni olmaydi ===
  tuzat=False: AttributeError: 'Musbat' object has no attribute 'nom'
  tuzat=True: narx=5000
  tuzat=True: ValueError: narx manfiy bo'lmasin: -1

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

Misol 3 — Klass funksiya dekoratori sifatida

python
"""Sanagich nusxasi funksiyani almashtiradi; metodlarda self yo'qoladi; __get__ bilan tuzatish."""

import functools
import types


class Sanagich:
    def __init__(self, funksiya):
        functools.update_wrapper(self, funksiya)
        self.funksiya = funksiya
        self.soni = 0

    def __call__(self, *args, **kwargs):
        self.soni += 1
        return self.funksiya(*args, **kwargs)


print("=== 1. Oddiy funksiya ===")


@Sanagich
def kvadrat(x):
    """x ning kvadrati."""
    return x * x


print(f"  {[kvadrat(i) for i in range(4)]}, soni={kvadrat.soni}")
print(f"  turi: {type(kvadrat).__name__}, __name__: {kvadrat.__name__}, __doc__: {kvadrat.__doc__!r}")
print(f"  asl funksiya: {kvadrat.__wrapped__.__name__}(5) = {kvadrat.__wrapped__(5)}, soni o'zgarmadi: {kvadrat.soni}")

print("\n=== 2. ❌ Metodda self bog'lanmaydi ===")


class Hisob:
    def __init__(self, balans):
        self.balans = balans

    @Sanagich
    def qoldiq(self):
        return self.balans


h = Hisob(700)
try:
    h.qoldiq()
except TypeError as e:
    print(f"  h.qoldiq(): TypeError: {e}")
print(f"  h.qoldiq turi: {type(h.qoldiq).__name__}   (oddiy funksiya bo'lsa — method)")
print(f"  qo'lda self bersak ishlaydi: Hisob.qoldiq(h) = {Hisob.qoldiq(h)}")
print(f"  oddiy funksiyada __get__ bor: {hasattr(lambda: 0, '__get__')}, Sanagich nusxasida: {hasattr(kvadrat, '__get__')}")

print("\n=== 3. ✅ __get__ bilan tuzatish ===")


class MetodSanagich(Sanagich):
    def __get__(self, obj, egasi=None):
        if obj is None:
            return self
        return types.MethodType(self, obj)


class Hisob2:
    def __init__(self, balans):
        self.balans = balans

    @MetodSanagich
    def qoldiq(self):
        return self.balans


a, b = Hisob2(100), Hisob2(250)
print(f"  a.qoldiq()={a.qoldiq()}, b.qoldiq()={b.qoldiq()}, a.qoldiq()={a.qoldiq()}")
print(f"  a.qoldiq turi: {type(a.qoldiq).__name__}")
print(f"  ⚠️ soni barcha nusxalar uchun umumiy: {Hisob2.qoldiq.soni}")

print("\n=== 4. Nusxa bo'yicha hisob kerak bo'lsa — closure asosidagi dekorator ===")


def nusxa_sanagich(funksiya):
    @functools.wraps(funksiya)
    def orovchi(self, *args, **kwargs):
        sanoq = self.__dict__.setdefault("_sanoq", {})
        sanoq[funksiya.__name__] = sanoq.get(funksiya.__name__, 0) + 1
        return funksiya(self, *args, **kwargs)
    return orovchi


class Hisob3:
    def __init__(self, balans):
        self.balans = balans

    @nusxa_sanagich
    def qoldiq(self):
        return self.balans


a, b = Hisob3(100), Hisob3(250)
a.qoldiq(); a.qoldiq(); b.qoldiq()
print(f"  a: {a._sanoq}, b: {b._sanoq}, turi: {type(a.qoldiq).__name__}")

Natijaning muhim qismi:

text
=== 1. Oddiy funksiya ===
  [0, 1, 4, 9], soni=4
  turi: Sanagich, __name__: kvadrat, __doc__: 'x ning kvadrati.'
  asl funksiya: kvadrat(5) = 25, soni o'zgarmadi: 4

=== 2. ❌ Metodda self bog'lanmaydi ===
  h.qoldiq(): TypeError: Hisob.qoldiq() missing 1 required positional argument: 'self'
  h.qoldiq turi: Sanagich   (oddiy funksiya bo'lsa — method)
  qo'lda self bersak ishlaydi: Hisob.qoldiq(h) = 700
  oddiy funksiyada __get__ bor: True, Sanagich nusxasida: False

=== 3. ✅ __get__ bilan tuzatish ===
  a.qoldiq()=100, b.qoldiq()=250, a.qoldiq()=100
  a.qoldiq turi: method
  ⚠️ soni barcha nusxalar uchun umumiy: 3

=== 4. Nusxa bo'yicha hisob kerak bo'lsa — closure asosidagi dekorator ===
  a: {'qoldiq': 2}, b: {'qoldiq': 1}, turi: method

Nima ko'rsatdi: 2.7-bo'lim. functools.wraps va update_wrapper — 10.11-darsda to'liq.

Misol 4 — Amaliy: ma'lumot modellari ro'yxati

Kichik "ORM-ga o'xshash" tizim: @model klassni ro'yxatga oladi, annotatsiyalardan maydonlarni tekshiradi va lugatga/lugatdan metodlarini qo'shadi. @dataclass bilan birga ishlatiladi.

python
"""@model: ro'yxat + tekshiruv + metod qo'shish; dataclass(slots=True) bilan tartib tuzog'i; __init_subclass__ muqobili."""

from dataclasses import dataclass, fields, is_dataclass

MODELLAR = {}


def model(klass=None, /, *, jadval=None):
    def dekorator(k):
        if not is_dataclass(k):
            raise TypeError(f"@model: {k.__name__} dataclass emas — @model ni @dataclass ustiga qo'ying")
        qollanmaydigan = [f.name for f in fields(k) if f.type not in (int, float, str, bool)]
        if qollanmaydigan:
            raise TypeError(f"@model: {k.__name__} da qo'llab-quvvatlanmaydigan turlar: {qollanmaydigan}")

        def lugatga(self):
            return {f.name: getattr(self, f.name) for f in fields(self)}

        @classmethod
        def lugatdan(cls, malumot):
            ortiqcha = set(malumot) - {f.name for f in fields(cls)}
            if ortiqcha:
                raise ValueError(f"{cls.__name__}: noma'lum maydonlar {sorted(ortiqcha)}")
            return cls(**{f.name: f.type(malumot[f.name]) for f in fields(cls) if f.name in malumot})

        for nom, qiymat in [("lugatga", lugatga), ("lugatdan", lugatdan)]:
            if nom not in k.__dict__:
                funk = qiymat.__func__ if isinstance(qiymat, classmethod) else qiymat
                funk.__qualname__ = f"{k.__qualname__}.{nom}"
                setattr(k, nom, qiymat)
        k.jadval = jadval or k.__name__.lower() + "lar"
        MODELLAR[k.jadval] = k
        return k

    return dekorator if klass is None else dekorator(klass)


print("=== 1. To'g'ri tartib ===")


@model
@dataclass(slots=True)
class Talaba:
    ism: str
    kurs: int
    grant: bool = False


@model(jadval="kitoblar")
@dataclass(frozen=True, slots=True)
class Kitob:
    nomi: str
    narx: float


t = Talaba.lugatdan({"ism": "Dilnoza", "kurs": "3", "grant": True})
print(f"  {t!r}")
print(f"  lugatga: {t.lugatga()}")
print(f"  MODELLAR: { {k: v.__name__ for k, v in MODELLAR.items()} }")
print(f"  ro'yxatdagi klass — yakuniy klass: {MODELLAR['talabalar'] is Talaba}, slots: {not hasattr(t, '__dict__')}")

print("\n=== 2. Import paytidagi tekshiruvlar ===")
for tavsif, yaratuvchi in [
    ("dataclass'siz", lambda: model(type("Xom", (), {}))),
    ("ro'yxat maydoni", lambda: model(dataclass(type("Guruh", (), {"__annotations__": {"a": list}})))),
]:
    try:
        yaratuvchi()
    except TypeError as e:
        print(f"  ❌ {tavsif}: {e}")
try:
    Talaba.lugatdan({"ism": "Aziz", "yosh": 20})
except ValueError as e:
    print(f"  ❌ {e}")

print("\n=== 3. ⚠️ Tartib xatosi: @dataclass(slots=True) yuqorida ===")
QAYD = {}


def qayd_qil(klass):
    QAYD[klass.__name__] = klass
    return klass


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


eski = QAYD["Nuqta"]
p = eski(1, 2)
print(f"  QAYD['Nuqta'] is Nuqta: {eski is Nuqta}")
print(f"  eski klassdan obyekt: {p}, isinstance(p, Nuqta): {isinstance(p, Nuqta)}")


def yangi_atribut(obyekt):
    try:
        obyekt.z = 0
        return "qo'shildi — slots yo'q"
    except AttributeError:
        return "AttributeError — slots ishlayapti"


print(f"  eski klass obyektiga z: {yangi_atribut(p)}")
print(f"  yakuniy klass obyektiga z: {yangi_atribut(Nuqta(1, 2))}")

print("\n=== 4. Merosxo'rlar: dekorator vs __init_subclass__ ===")


@model
@dataclass
class Xodim:
    ism: str
    maosh: int


@dataclass
class Oqituvchi(Xodim):
    fan: str = "matematika"


print(f"  Oqituvchi ro'yxatda: {'oqituvchilar' in MODELLAR}, lugatga meros: {Oqituvchi('Aziz', 9, 'fizika').lugatga()}")


class Model:
    royxat = {}

    def __init_subclass__(cls, /, jadval=None, **kwargs):
        super().__init_subclass__(**kwargs)
        cls.jadval = jadval or cls.__name__.lower() + "lar"
        Model.royxat[cls.jadval] = cls


class Mijoz(Model, jadval="mijozlar"):
    pass


class VIPMijoz(Mijoz):
    pass


print(f"  __init_subclass__: {sorted(Model.royxat)}  ⚠️ lekin bu yerda klass hali dataclass emas —")
print("     __init_subclass__ dekoratorlardan OLDIN ishlaydi (2.1, 5-qadam)")

Natijaning muhim qismi:

text
=== 1. To'g'ri tartib ===
  Talaba(ism='Dilnoza', kurs=3, grant=True)
  lugatga: {'ism': 'Dilnoza', 'kurs': 3, 'grant': True}
  MODELLAR: {'talabalar': 'Talaba', 'kitoblar': 'Kitob'}
  ro'yxatdagi klass — yakuniy klass: True, slots: True

=== 2. Import paytidagi tekshiruvlar ===
  ❌ dataclass'siz: @model: Xom dataclass emas — @model ni @dataclass ustiga qo'ying
  ❌ ro'yxat maydoni: @model: Guruh da qo'llab-quvvatlanmaydigan turlar: ['a']
  ❌ Talaba: noma'lum maydonlar ['yosh']

=== 3. ⚠️ Tartib xatosi: @dataclass(slots=True) yuqorida ===
  QAYD['Nuqta'] is Nuqta: False
  eski klassdan obyekt: Nuqta(x=1, y=2), isinstance(p, Nuqta): False
  eski klass obyektiga z: qo'shildi — slots yo'q
  yakuniy klass obyektiga z: AttributeError — slots ishlayapti

=== 4. Merosxo'rlar: dekorator vs __init_subclass__ ===
  Oqituvchi ro'yxatda: False, lugatga meros: {'ism': 'Aziz', 'maosh': 9, 'fan': 'fizika'}
  __init_subclass__: ['mijozlar', 'vipmijozlar']  ⚠️ lekin bu yerda klass hali dataclass emas —
     __init_subclass__ dekoratorlardan OLDIN ishlaydi (2.1, 5-qadam)

Nima ko'rsatdi: 2.3, 2.5, 2.6-bo'limlar — klass dekoratori ro'yxatga olish, tekshirish va metod qo'shishni bir joyda qila oladi, lekin slots=True bilan tartib va meros bilan cheklovni hisobga olish kerak.


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

Noto'g'ri fikr To'g'risi
"Klass dekoratori klass yaratilishidan oldin ishlaydi" Tayyor klassni oladi — __set_name__ va __init_subclass__ dan keyin
"Merosxo'r klassga ham dekorator qo'llanadi" Yo'q — faqat natijasi meros qilinadi
"hasattr(klass, '__repr__') — o'zi yozganini tekshiradi" object dan meros bo'lgani uchun har doim True
"Keyin qo'shilgan deskriptor oddiy ishlaydi" __set_name__ chaqirilmaydi
"vars(klass) dagi hamma narsa funksiya" staticmethod, classmethod, property — o'rovchi obyektlar
"@dataclass har doim o'sha klassni qaytaradi" slots=True — yangi klass
"Klassni funksiya bilan almashtirish zararsiz" isinstance, meros, klass atributlari buziladi
"__call__ li klass metodlarni ham bezatadi" __get__ bo'lmasa self bog'lanmaydi

6. Keng tarqalgan xatolar va yechimlari

1. Klass dekoratorida return ni unutish

python
def royxatga_ol(klass):
    ROYXAT.append(klass)
                                    # ❌ klass nomi None ga bog'landi

def royxatga_ol(klass):
    ROYXAT.append(klass)
    return klass                    # ✅

2. Foydalanuvchi metodini ustiga yozish

python
def avto_repr(klass):
    if not hasattr(klass, "__repr__"):      # ❌ har doim False — tekshiruv ishlamaydi
        ...
    klass.__repr__ = __repr__               # ❌ o'zi yozganini o'chirdi

    if "__repr__" not in klass.__dict__:    # ✅
        klass.__repr__ = __repr__

3. staticmethod/classmethod ni to'g'ridan-to'g'ri o'rash

python
for nom, qiymat in vars(klass).items():
    setattr(klass, nom, jurnal(qiymat))     # ❌ classmethod obyektini oddiy funksiyaga o'radi

if isinstance(qiymat, (staticmethod, classmethod)):
    setattr(klass, nom, type(qiymat)(jurnal(qiymat.__func__)))   # ✅

4. Singleton'ni funksiya bilan

python
@singleton                                  # ❌ isinstance va meros buziladi
class Sozlama: ...

# ✅ modul darajasidagi bitta nusxa — eng oddiy va Python uslubida
sozlama = Sozlama()

5. Merosxo'rlar ro'yxatga tushadi deb kutish

python
@plagin
class Baza: ...
class CSV(Baza): ...                        # ❌ ro'yxatda yo'q

class Baza:
    def __init_subclass__(cls, **kw):       # ✅
        super().__init_subclass__(**kw)
        PLAGINLAR.append(cls)

6. Ro'yxatga oluvchi @dataclass(slots=True) ostida

python
@dataclass(slots=True)
@royxatga_ol                                # ❌ eski klass saqlandi
class Nuqta: ...

@royxatga_ol                                # ✅
@dataclass(slots=True)
class Nuqta: ...

7. Keyin qo'shilgan deskriptor

python
klass.narx = Musbat()                       # ❌ __set_name__ yo'q → AttributeError

d = Musbat()
klass.narx = d
d.__set_name__(klass, "narx")               # ✅

8. __call__ li klassni metod dekoratori sifatida

python
class Sanagich:
    def __call__(self, *a): ...

class Hisob:
    @Sanagich
    def qoldiq(self): ...                   # ❌ missing 'self'

# ✅ __get__ qo'shing yoki closure asosidagi dekorator yozing

7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 8.12, 8.17-darslar (o'tilgan): total_ordering, dataclass — klass dekoratorlari
  • 8.19-dars (o'tilgan): __slots__ — slots=True nega yangi klass talab qiladi
  • 10.8–10.9-darslar (o'tilgan): @ ochilishi, tartib, argumentli dekoratorlar
  • 10.11-dars: functools.update_wrapper — Sanagich da ishlatildi
  • 10.16-dars: __new__ — singleton'ning to'g'ri usuli
  • 10.17-dars: deskriptor protokoli, __get__, __set_name__
  • 17-qism: mock.patch klass dekoratori sifatida
  • 13, 20, 23-qismlar: Pydantic va SQLAlchemy modellari — __init_subclass__ va metaklasslar amalda

8. Eng yaxshi amaliyotlar

  1. Klass dekoratori klassni o'zini qaytarsin. Almashtirish kerak bo'lsa — jiddiy sabab va hujjat bilan.

  2. Mavjud narsani klass.__dict__ orqali tekshiring, hasattr bilan emas.

  3. Qo'shilgan funksiyalarning __qualname__ ini tuzating.

  4. Barcha metodlarni o'raganda staticmethod, classmethod, property va dunderlarni alohida ko'rib chiqing.

  5. Merosxo'rlarga ham kerak bo'lsa — __init_subclass__. Tanlab qo'shiladigan xususiyat — dekorator.

  6. Ro'yxatga oluvchi dekorator — eng yuqorida, ayniqsa @dataclass(slots=True) bilan.

  7. Import paytida tekshiring — noto'g'ri turdagi maydon, tartib xatosi, yetishmayotgan metod.

  8. __call__ li klassni metodlarda ishlatsangiz — __get__ qo'shing yoki closure asosidagi dekoratorni tanlang.


9. Amaliy topshiriq

Vazifa 1: Natijani bashorat qiling

python
from dataclasses import dataclass

royxat = []
def ol(cls):
    royxat.append(cls.__name__)
    return cls

def salom_qosh(cls):
    cls.salom = lambda self: f"salom, {type(self).__name__}"
    return cls

def funksiyaga(cls):
    return lambda *a: cls(*a)

class Sanoq:
    def __init__(self, f):
        self.f = f
        self.n = 0
    def __call__(self, *a):
        self.n += 1
        return self.f(*a)
1.  @ol
    class A:
        pass
    class B(A):
        pass
    print(royxat)
2.  @salom_qosh
    class K:
        pass
    print(K().salom())
3.  @salom_qosh
    class K:
        pass
    class L(K):
        pass
    print(L().salom())
4.  @funksiyaga
    class M:
        pass
    print(type(M).__name__)
5.  @funksiyaga
    class M:
        pass
    try:
        isinstance(M(), M)
    except TypeError:
        print("TypeError")
6.  print(hasattr(type("N", (), {}), "__repr__"))
7.  royxat.clear()
    @ol
    @salom_qosh
    class P:
        pass
    print(royxat, hasattr(P, "salom"))
8.  @Sanoq
    def f(x):
        return x + 1
    f(1)
    f(2)
    print(f.n, type(f).__name__)
9.  class Q:
        @Sanoq
        def m(self):
            return 1
    try:
        Q().m()
    except TypeError:
        print("TypeError")
10. @dataclass(slots=True)
    class S:
        x: int
    print(hasattr(S(1), "__dict__"))
11. def d(cls):
        cls.n = cls.__name__.lower()
        return cls
    @d
    class Kitob:
        pass
    class Jurnal(Kitob):
        pass
    print(Kitob.n, Jurnal.n)
12. class Baza:
        def __init_subclass__(cls, **kw):
            super().__init_subclass__(**kw)
            cls.n = cls.__name__.lower()
    class Kitob(Baza):
        pass
    class Jurnal(Kitob):
        pass
    print(Jurnal.n, hasattr(Baza, "n"))
Javoblar
  1. ['A'] — dekorator merosxo'rga qo'llanmaydi
  2. salom, K
  3. salom, L — qo'shilgan metod meros qilinadi, type(self) esa L
  4. function — klass funksiya bilan almashtirildi
  5. TypeError — M endi tur emas
  6. True — object dan meros
  7. ['P'] True
  8. 2 Sanoq — f endi Sanoq nusxasi
  9. TypeError — self bog'lanmadi
  10. False — slots=True
  11. kitob kitob — n atributi meros qilindi, dekorator qayta ishlamadi
  12. jurnal False — __init_subclass__ har merosxo'rda ishladi, bazaning o'zida emas

Vazifa 2: Xatolarni tuzating

python
1.  def vaqt_belgisi(klass):
        klass.yaratilgan = datetime.now()

2.  def jurnal_bilan(klass):
        for nom in dir(klass):
            if not nom.startswith("_"):
                setattr(klass, nom, jurnal(getattr(klass, nom)))
        return klass

3.  def avto_eq(klass):
        if not hasattr(klass, "__eq__"):
            klass.__eq__ = lambda a, b: vars(a) == vars(b)
        return klass

4.  @dataclass(slots=True, frozen=True)
    @royxatga_ol
    class Buyurtma:
        raqam: int

5.  class Kesh:
        def __init__(self, f):
            self.f, self.saqlangan = f, {}
        def __call__(self, *a):
            if a not in self.saqlangan:
                self.saqlangan[a] = self.f(*a)
            return self.saqlangan[a]
    class Hisobot:
        @Kesh
        def tuz(self, oy): ...
Javoblar
python
1.  def vaqt_belgisi(klass):
        klass.yaratilgan = datetime.now()
        return klass                        # return yo'q edi

2.  def jurnal_bilan(klass):
        # dir() meros qilinganlarni ham beradi, getattr esa staticmethod/classmethod ni ochib yuboradi
        for nom, qiymat in list(vars(klass).items()):
            if nom.startswith("_"):
                continue
            if isinstance(qiymat, (staticmethod, classmethod)):
                setattr(klass, nom, type(qiymat)(jurnal(qiymat.__func__)))
            elif callable(qiymat):
                setattr(klass, nom, jurnal(qiymat))
        return klass

3.  def avto_eq(klass):
        if "__eq__" not in klass.__dict__:  # hasattr har doim True
            def __eq__(a, b):
                return vars(a) == vars(b) if type(a) is type(b) else NotImplemented
            klass.__eq__ = __eq__
        return klass

4.  @royxatga_ol                            # yakuniy (slots) klass ro'yxatga tushsin
    @dataclass(slots=True, frozen=True)
    class Buyurtma:
        raqam: int

5.  class Kesh:
        ...
        def __get__(self, obj, egasi=None):
            return self if obj is None else types.MethodType(self, obj)
    # ⚠️ kesh kaliti (obj, oy) bo'ladi — obyektlar keshda abadiy saqlanadi (xotira sizishi);
    # ko'pincha functools.cached_property yoki nusxa lug'atidagi kesh yaxshiroq

Vazifa 3: @maydonlarni_tekshir

Annotatsiyali oddiy klass (dataclass emas) uchun dekorator yozing:

  1. Har annotatsiyalangan maydon uchun __set_name__ ni qo'lda chaqirgan holda turini tekshiruvchi deskriptor qo'shsin
  2. __init__ ni klassning o'zi yozmagan bo'lsa — maydonlar tartibida nomli va pozitsion argumentli __init__ qo'shsin
  3. Klass o'zi yozgan __init__ saqlansin
  4. Meros: ota klass maydonlari ham tekshirilsin (MRO bo'yicha annotatsiyalarni yig'ing)

Vazifa 4: @barcha_metodlar(dekorator, *, dunder=False, meros=False)

  1. Istalgan funksiya dekoratorini klassning barcha metodlariga qo'llasin
  2. staticmethod, classmethod, property (getter, setter, deleter) to'g'ri ishlasin
  3. meros=True — MRO bo'yicha ota klass metodlarini ham o'rab, klassning o'ziga qo'shsin (ota klassni o'zgartirmasin)
  4. Bir metod ikki marta o'ralmasin (belgi atributi bilan)

Vazifa 5: Plagin tizimi — ikki usulda

Fayl formatlari uchun plagin tizimini yozing (csv, json, tsv — tsv csv dan meros oladi):

  1. Klass dekoratori bilan
  2. __init_subclass__ bilan
  3. Har ikki usulda: takroriy nom — import paytida ValueError, abstrakt oraliq klasslar ro'yxatga tushmasin
  4. Qaysi usulda nima qulayroq ekanini jadvalda solishtiring

Vazifa 6: MetodKesh — to'g'ri metod keshi

__call__ va __get__ li MetodKesh klassini yozing:

  1. Kesh har nusxa uchun alohida bo'lsin va nusxa o'chirilganda yo'qolsin (weakref.WeakKeyDictionary)
  2. obj.metod.tozala() — faqat shu nusxa keshini tozalasin
  3. Klass.metod.statistika() — barcha nusxalar bo'yicha topildi/topilmadi
  4. functools.update_wrapper bilan metama'lumot saqlansin

Vazifa 7: O'ylash

@dataclass klass dekoratori sifatida yozilgan. Xuddi shu vazifani Model bazaviy klassi va __init_subclass__ bilan, yoki metaklass bilan ham qilish mumkin edi (Pydantic va Django shunday qiladi). PEP 557 mualliflari nega dekoratorni tanladi? Loyihangizda qachon qaysi usulni tanlaysiz?

Javob

Qisqa javob: dekorator klassga hech qanday baza yoki metaklass majburlamaydi — u tayyor klassni oladi, kerakli metodlarni qo'shadi va o'sha klassni qaytaradi. Natijada dataclass oddiy klassdan farq qilmaydi: istalgan meros ierarxiyasiga, istalgan metaklassga mos keladi va uni olib tashlash oson. Bazaviy klass va metaklass esa ierarxiya bo'yicha avtomatik tarqaladigan qoidalar uchun kuchliroq — lekin ular klassning "nasl-nasabiga" aralashadi.

1. PEP 557 ning asosiy dalillari

PEP 557 (Python 3.7) da ta'kidlanganidek, dataclass hech qanday bazaviy klass yoki metaklassdan foydalanmaydi — foydalanuvchilar meros va metaklasslardan o'z maqsadlari uchun erkin foydalanishi mumkin. Dekorator klassni o'rganadi (annotatsiyalar), metodlarni yozadi va klassni qaytaradi — "oddiy klass, faqat qo'lda yozish kerak bo'lmagan metodlar bilan".

2. Metaklass to'qnashuvi

python
class A(metaclass=MetaA): ...
class B(metaclass=MetaB): ...
class C(A, B): ...           # TypeError: metaclass conflict

Agar dataclass metaklass bo'lganida, uni ABC (metaklassi ABCMeta), Django modellari yoki boshqa metaklassli kutubxonalar bilan birlashtirish qiyin bo'lardi. Dekorator bu muammoni butunlay chetlab o'tadi:

python
@dataclass
class Shakl(ABC):            # ✅ ABCMeta bilan muammo yo'q
    nom: str

3. Bazaviy klass bilan muammolar

class Nuqta(DataClass) ko'rinishida:

  • har dataclass MRO siga qo'shimcha klass tushadi
  • ko'p merosda bazaviy klassning o'z __init_subclass__ yoki metodlari boshqa bazalar bilan to'qnashishi mumkin
  • "bu klass ma'lumot klassimi?" degan savol meros bilan bog'lanib qoladi, holbuki bu xususiyat, tur emas

4. Dekoratorning kamchiliklari

  • Meros bo'lmaydi — har merosxo'rga @dataclass ni qayta yozish kerak (bu ataylab: merosxo'rning o'z maydonlari bo'lishi mumkin)
  • Statik tahlil — tur tekshiruvchilar dekoratorning nima qo'shganini bilishi uchun maxsus qo'llab-quvvatlash kerak bo'ldi (keyinchalik typing.dataclass_transform, PEP 681, bu muammoni umumiy hal qildi)
  • slots=True kabi imkoniyatlar klassni almashtirishga majbur qiladi

5. Pydantic va Django nega baza/metaklass tanlagan

  • Django modellari: har model ma'lumotlar bazasi jadvaliga bog'lanadi, merosxo'r modellar ham avtomatik ro'yxatga tushishi, maydonlar esa meros bo'yicha birlashishi kerak. Bu ierarxiya qoidasi — metaklass tabiiy joy
  • Pydantic: BaseModel merosxo'rlari avtomatik ravishda validatsiya, serializatsiya va JSON sxemaga ega bo'ladi; konfiguratsiya ham meros qilinadi. Baza klass shu umumiy xatti-harakatning markazi

6. Qaysi usulni qachon tanlash

Savol Dekorator __init_subclass__ Metaklass
Xususiyat tanlab qo'shiladimi?
Merosxo'rlarga avtomatik tarqalishi kerakmi?
Klass tanasi bajarilishidan oldin aralashish kerakmi (__prepare__)?
Klassni boshqa obyekt bilan almashtirish kerakmi? (ehtiyot bilan)
Boshqa kutubxonalar bilan moslik muhimmi? eng yaxshi yaxshi to'qnashuv xavfi
Kod o'quvchiga aniq ko'rinishi kerakmi? @ ko'rinib turadi Bazada yashirin Bazada yashirin

7. Amaliy qoida

  1. Avval dekorator yoki oddiy funksiya — eng kam sehr
  2. Ierarxiya bo'yicha qoida kerak bo'lsa — __init_subclass__ (PEP 487 aynan metaklasslarning keng tarqalgan ishlatilishlarini oddiyroq qilish uchun kiritilgan)
  3. Faqat klass yaratilishining o'zini boshqarish kerak bo'lsa (tana nom maydoni, __prepare__, klassni ro'yxatdan oldin o'zgartirish) — metaklass

8. Xulosa

  1. dataclass dekorator, chunki u baza va metaklass majburlamaydi — istalgan ierarxiyaga mos
  2. Dekorator meros bo'lmaydi va tur tekshiruvchilar uchun maxsus qo'llab-quvvatlash talab qiladi
  3. Django va Pydantic merosxo'rlarga avtomatik tarqaladigan xatti-harakat uchun baza/metaklassni tanlagan
  4. Tanlov mezoni: xususiyat tanlabmi yoki ierarxiya qoidasimi, va boshqa kutubxonalar bilan moslik qanchalik muhim
  5. Eng kam kuchli vosita yetsa — o'shani tanlang: dekorator → __init_subclass__ → metaklass

Nimani mustahkamlaydi: 2.1–2.8-bo'limlar.


Xulosa

Bu darsda klass dekoratorlarini va klassni dekorator sifatida ishlatishni o'rgandik.

Eng muhim uch fikr:

  1. @dek class K — bu K = dek(K), tayyor klass ustida. Dekorator klass tanasi, __set_name__ va __init_subclass__ dan keyin ishlaydi. Eng xavfsiz strategiya — ro'yxatga olish yoki joyida o'zgartirib, o'sha klassni qaytarish. Klassni funksiya bilan almashtirish isinstance, meros va klass atributlarini buzadi.

  2. Joyida o'zgartirishning nozik joylari bor. Klass o'zi aniqlagan metodni klass.__dict__ orqali tekshiring — hasattr doim True. Qo'shilgan funksiyalarning __qualname__ ini tuzating, keyin qo'shilgan deskriptor uchun __set_name__ ni qo'lda chaqiring. Barcha metodlarni o'raganda staticmethod, classmethod va property ni alohida ishlang, meros qilingan metodlar esa vars(klass) da yo'qligini unutmang.

  3. Dekorator meros bo'lmaydi va tartib muhim. Merosxo'rlarga avtomatik qo'llanishi kerak bo'lgan qoida — __init_subclass__ ga. dataclass(slots=True) yangi klass qaytaradi, shuning uchun ro'yxatga oluvchi dekorator eng yuqorida bo'lishi kerak. Klass funksiya dekoratori bo'lganda holat ochiq atributlarda turadi, lekin metodlarda self bog'lanishi uchun __get__ kerak.

Keyingi darsda functools.wraps va metama'lumotni o'rganamiz: dekorator nimalarni yashiradi, __wrapped__ qanday ishlaydi, inspect.signature va help() o'ralgan funksiyani qanday ko'radi.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
10.10-dars: Klass dekoratori — IlmHamroh