Mundarija (22)
- 1. Kirish va motivatsiya
- 2. Nazariya — chuqur tushuntirish
- 2.1. Ochilishi va lahzasi
- 2.2. Uch strategiya
- 2.3. Klassga metod qo'shish
- 2.4. Barcha metodlarni o'rash
- 2.5. Dekoratorlar meros bo'lmaydi
- 2.6. dataclass(slots=True) — yangi klass
- 2.7. Klass — funksiya dekoratori sifatida
- 2.8. Standart kutubxonadagi klass dekoratorlari
- 3. Tez ma'lumotnoma
- 4. Batafsil misollar
- Misol 1 — Klass yaratilishi va dekorator lahzasi
- Misol 2 — Klassni joyida o'zgartirish
- Misol 3 — Klass funksiya dekoratori sifatida
- Misol 4 — Amaliy: ma'lumot modellari ro'yxati
- 5. To'g'ri va noto'g'ri tushunishlar
- 6. Keng tarqalgan xatolar va yechimlari
- 7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 8. Eng yaxshi amaliyotlar
- 9. Amaliy topshiriq
- Xulosa
10.10-dars: Klass dekoratori
10-QISM — ILG'OR TIL IMKONIYATLARI · 10-dars
1. Kirish va motivatsiya
Internetda tez-tez uchraydigan "singleton" dekoratori:
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:
isinstance(obj, Sozlama) # TypeError: isinstance() arg 2 must be a type, ...
class TestSozlama(Sozlama): ... # TypeError: function() argument 'code' must be code, not strChunki 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
selftuzog'i
2. Nazariya — chuqur tushuntirish
2.1. Ochilishi va lahzasi
@A
@B
class K:
...class K:
...
K = A(B(K))Klass yaratilish tartibi:
- Dekorator ifodalari (
A,B) baholanadi - Klass tanasi bajariladi — atributlar va metodlar lug'atga yig'iladi
- Metaklass (odatda
type) klass obyektini yaratadi - Tanadagi har bir obyekt uchun
__set_name__(klass, nom)chaqiriladi - Ota klassning
__init_subclass__chaqiriladi - Dekoratorlar pastdan yuqoriga qo'llanadi:
B, keyinA - Natija
Knomiga 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
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 klassUch 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:
deskriptor = Maydon()
klass.narx = deskriptor
deskriptor.__set_name__(klass, "narx") # ⭐2.4. Barcha metodlarni o'rash
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 klassTuzoqlar:
vars(klass)dastaticmethodvaclassmethod— 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 kerakproperty— 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 jurnalrepr(self)chaqirsa)
2.5. Dekoratorlar meros bo'lmaydi
@royxatga_ol
class Plagin: ...
class CSVPlagin(Plagin): ... # ❌ ro'yxatda yo'qDekorator 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):
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.
@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.
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 atributhisobla endi Sanagich nusxasi. Oddiy funksiyalar uchun zo'r — holat ochiq. Lekin metodlarda buziladi:
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):
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 → nomShablonlar
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 klassQoidalar
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 buziladiKlass funksiya dekoratori sifatida
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
"""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:
=== 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
"""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:
=== 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: -1Nima ko'rsatdi: 2.3, 2.4-bo'limlar.
Misol 3 — Klass funksiya dekoratori sifatida
"""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:
=== 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: methodNima 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.
"""@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:
=== 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
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
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
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
@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
@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
@dataclass(slots=True)
@royxatga_ol # ❌ eski klass saqlandi
class Nuqta: ...
@royxatga_ol # ✅
@dataclass(slots=True)
class Nuqta: ...7. Keyin qo'shilgan deskriptor
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
class Sanagich:
def __call__(self, *a): ...
class Hisob:
@Sanagich
def qoldiq(self): ... # ❌ missing 'self'
# ✅ __get__ qo'shing yoki closure asosidagi dekorator yozing7. 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=Truenega yangi klass talab qiladi - 10.8–10.9-darslar (o'tilgan):
@ochilishi, tartib, argumentli dekoratorlar - 10.11-dars:
functools.update_wrapper—Sanagichda ishlatildi - 10.16-dars:
__new__— singleton'ning to'g'ri usuli - 10.17-dars: deskriptor protokoli,
__get__,__set_name__ - 17-qism:
mock.patchklass dekoratori sifatida - 13, 20, 23-qismlar: Pydantic va SQLAlchemy modellari —
__init_subclass__va metaklasslar amalda
8. Eng yaxshi amaliyotlar
Klass dekoratori klassni o'zini qaytarsin. Almashtirish kerak bo'lsa — jiddiy sabab va hujjat bilan.
Mavjud narsani
klass.__dict__orqali tekshiring,hasattrbilan emas.Qo'shilgan funksiyalarning
__qualname__ini tuzating.Barcha metodlarni o'raganda
staticmethod,classmethod,propertyva dunderlarni alohida ko'rib chiqing.Merosxo'rlarga ham kerak bo'lsa —
__init_subclass__. Tanlab qo'shiladigan xususiyat — dekorator.Ro'yxatga oluvchi dekorator — eng yuqorida, ayniqsa
@dataclass(slots=True)bilan.Import paytida tekshiring — noto'g'ri turdagi maydon, tartib xatosi, yetishmayotgan metod.
__call__li klassni metodlarda ishlatsangiz —__get__qo'shing yoki closure asosidagi dekoratorni tanlang.
9. Amaliy topshiriq
Vazifa 1: Natijani bashorat qiling
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
['A']— dekorator merosxo'rga qo'llanmaydisalom, Ksalom, L— qo'shilgan metod meros qilinadi,type(self)esaLfunction— klass funksiya bilan almashtirildiTypeError—Mendi tur emasTrue—objectdan meros['P'] True2 Sanoq—fendiSanoqnusxasiTypeError—selfbog'lanmadiFalse—slots=Truekitob kitob—natributi meros qilindi, dekorator qayta ishlamadijurnal False—__init_subclass__har merosxo'rda ishladi, bazaning o'zida emas
Vazifa 2: Xatolarni tuzating
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
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 yaxshiroqVazifa 3: @maydonlarni_tekshir
Annotatsiyali oddiy klass (dataclass emas) uchun dekorator yozing:
- Har annotatsiyalangan maydon uchun
__set_name__ni qo'lda chaqirgan holda turini tekshiruvchi deskriptor qo'shsin __init__ni klassning o'zi yozmagan bo'lsa — maydonlar tartibida nomli va pozitsion argumentli__init__qo'shsin- Klass o'zi yozgan
__init__saqlansin - Meros: ota klass maydonlari ham tekshirilsin (MRO bo'yicha annotatsiyalarni yig'ing)
Vazifa 4: @barcha_metodlar(dekorator, *, dunder=False, meros=False)
- Istalgan funksiya dekoratorini klassning barcha metodlariga qo'llasin
staticmethod,classmethod,property(getter, setter, deleter) to'g'ri ishlasinmeros=True— MRO bo'yicha ota klass metodlarini ham o'rab, klassning o'ziga qo'shsin (ota klassni o'zgartirmasin)- 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):
- Klass dekoratori bilan
__init_subclass__bilan- Har ikki usulda: takroriy nom — import paytida
ValueError, abstrakt oraliq klasslar ro'yxatga tushmasin - Qaysi usulda nima qulayroq ekanini jadvalda solishtiring
Vazifa 6: MetodKesh — to'g'ri metod keshi
__call__ va __get__ li MetodKesh klassini yozing:
- Kesh har nusxa uchun alohida bo'lsin va nusxa o'chirilganda yo'qolsin (
weakref.WeakKeyDictionary) obj.metod.tozala()— faqat shu nusxa keshini tozalasinKlass.metod.statistika()— barcha nusxalar bo'yicha topildi/topilmadifunctools.update_wrapperbilan 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
class A(metaclass=MetaA): ...
class B(metaclass=MetaB): ...
class C(A, B): ... # TypeError: metaclass conflictAgar 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:
@dataclass
class Shakl(ABC): # ✅ ABCMeta bilan muammo yo'q
nom: str3. 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
@dataclassni 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=Truekabi 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:
BaseModelmerosxo'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
- Avval dekorator yoki oddiy funksiya — eng kam sehr
- Ierarxiya bo'yicha qoida kerak bo'lsa —
__init_subclass__(PEP 487 aynan metaklasslarning keng tarqalgan ishlatilishlarini oddiyroq qilish uchun kiritilgan) - Faqat klass yaratilishining o'zini boshqarish kerak bo'lsa (tana nom maydoni,
__prepare__, klassni ro'yxatdan oldin o'zgartirish) — metaklass
8. Xulosa
dataclassdekorator, chunki u baza va metaklass majburlamaydi — istalgan ierarxiyaga mos- Dekorator meros bo'lmaydi va tur tekshiruvchilar uchun maxsus qo'llab-quvvatlash talab qiladi
- Django va Pydantic merosxo'rlarga avtomatik tarqaladigan xatti-harakat uchun baza/metaklassni tanlagan
- Tanlov mezoni: xususiyat tanlabmi yoki ierarxiya qoidasimi, va boshqa kutubxonalar bilan moslik qanchalik muhim
- 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:
@dek class K— buK = 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 almashtirishisinstance, meros va klass atributlarini buzadi.Joyida o'zgartirishning nozik joylari bor. Klass o'zi aniqlagan metodni
klass.__dict__orqali tekshiring —hasattrdoimTrue. Qo'shilgan funksiyalarning__qualname__ini tuzating, keyin qo'shilgan deskriptor uchun__set_name__ni qo'lda chaqiring. Barcha metodlarni o'ragandastaticmethod,classmethodvapropertyni alohida ishlang, meros qilingan metodlar esavars(klass)da yo'qligini unutmang.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 metodlardaselfbog'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.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!