Mundarija (23)
- 1. Kirish va motivatsiya
- 2. Nazariya — chuqur tushuntirish
- 2.1. Protokol
- 2.2. obj.nom — to'liq algoritm
- 2.3. obj.nom = qiymat
- 2.4. Klassdan murojaat: Klass.nom
- 2.5. Holat qayerda saqlanadi
- 2.6. Tuzoqlar
- 2.7. Bog'langan metodlar
- 2.8. Qachon nima
- 3. Tez ma'lumotnoma
- 4. Batafsil misollar
- Misol 1 — __getattribute__ va __setattr__ ni qayta yozish
- Misol 2 — Holat qayerda saqlanadi va tuzoqlar
- Misol 3 — O'rnatilgan deskriptorlarni sof Python'da yozish
- Misol 4 — Amaliy: maydonlar asosidagi validatsiya freymvorki
- 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-qism yakuni
10.17-dars: Descriptor protokoli
10-QISM — ILG'OR TIL IMKONIYATLARI · 17-dars
1. Kirish va motivatsiya
8.15-darsda property deskriptor ekanini, data va non-data deskriptorlar farqini va atributga murojaat tartibini ko'rdik. 8.4, 8.16 va 8.19-darslarda metodlar, classmethod, staticmethod va __slots__ ham shu protokolga tayanishini bildik. 10-qism davomida esa deskriptorlar bilan qayta-qayta uchrashdik: partial 3.14 da self ni bog'lashi 10.13-bob, Sanagich klassi metodda self ni yo'qotishi 10.10-bob, super() ning __class__ hujayrasi 10.12-bob.
Endi yakuniy savol — atributga murojaatning butun mexanizmi. Mana real xato:
class Konfig:
def __init__(self, fayl):
self._malumot = json.load(open(fayl))
def __getattr__(self, nom):
return self._malumot.get(nom, "sukut")
@property
def ulanish_satri(self):
return f"postgres://{self.host}:{self.port}/{self.baza_nomi}"konfig.ulanish_satri → "sukut". Nega butun satr emas, "sukut"?
baza_nomi o'rniga fayldagi kalit baza edi va self.baza_nomi ... yo'q, __getattr__ uni ham "sukut" bilan qaytarardi. Aslida xato boshqa joyda: self.port butun son emas, None edi va f-string emas, boshqa bir qatorda AttributeError chiqdi. Property ichidagi har qanday AttributeError Python uchun "atribut topilmadi" degani — u __getattr__ ga o'tadi va haqiqiy xato jim yo'qoladi.
Deskriptor protokoli — Python obyekt modelining "yashirin dvigateli": metodlar, property, classmethod, staticmethod, __slots__, super() va ko'plab freymvorklar (Django modellari, SQLAlchemy ustunlari, attrs, Pydantic) shunga qurilgan. Uni ichidan bilgan dasturchi atribut qayerdan kelayotganini aniq ayta oladi.
Bu darsda:
-
object.__getattribute__va__setattr__ni Python'da qayta yozamiz - Klassdan murojaat:
__get__(None, klass) - Deskriptor holatni qayerda saqlashi kerak — to'rt variant
- Tuzoqlar: bir deskriptor ikki nomda, klassga tayinlash,
__slots__to'qnashuvi,__getattr__yashiradigan xatolar - Bog'langan metodlar va o'rnatilgan deskriptorlarni sof Python'da yozish
- Maydonlar asosidagi validatsiya freymvorki
2. Nazariya — chuqur tushuntirish
2.1. Protokol
class Deskriptor:
def __get__(self, obj, egasi=None): ... # o'qish
def __set__(self, obj, qiymat): ... # yozish
def __delete__(self, obj): ... # o'chirish
def __set_name__(self, egasi, nom): ... # klass yaratilganda (3.6+)| Tur | Metodlar | Nusxa __dict__ ga nisbatan |
|---|---|---|
| Data deskriptor | __set__ yoki __delete__ bor |
Ustun — lug'atdagi bir xil nomli qiymat e'tiborsiz |
| Non-data deskriptor | Faqat __get__ |
Past — lug'atdagi qiymat uni yashiradi |
Deskriptor faqat klass atributi bo'lganda ishlaydi. Nusxaning __dict__ idagi deskriptor obyekti — oddiy qiymat.
2.2. obj.nom — to'liq algoritm
object.__getattribute__(obj, nom):
def oqi(obj, nom):
tur = type(obj)
klass_attr = _mro_dan_qidir(tur, nom) # type(obj).__mro__ bo'ylab __dict__ lar
get = _mro_dan_qidir(type(klass_attr), "__get__")
if get and _data_mi(klass_attr): # 1. data deskriptor
return get(klass_attr, obj, tur)
if nom in getattr(obj, "__dict__", {}): # 2. nusxa lug'ati
return obj.__dict__[nom]
if get: # 3. non-data deskriptor
return get(klass_attr, obj, tur)
if klass_attr is not TOPILMADI: # 4. oddiy klass atributi
return klass_attr
raise AttributeError(nom)Keyin type(obj).__getattribute__ AttributeError ko'tarsa va klassda __getattr__ bo'lsa — __getattr__(obj, nom) chaqiriladi.
Muhim nuqtalar:
- Deskriptor va uning
__get__i turdan qidiriladi (10.15-dars) - Data deskriptor nusxa lug'atidan oldin, non-data — keyin
__getattr__— oxirgi zaxira, faqatAttributeErrorda. Deskriptor ichidan chiqqanAttributeErrorham shu yerga tushadi
2.3. obj.nom = qiymat
object.__setattr__(obj, nom, qiymat):
type(obj).__mro__da nom data deskriptor bo'lsa →__set__(obj, qiymat)(__set__yo'q, faqat__delete__bo'lsa —AttributeError)- Aks holda →
obj.__dict__[nom] = qiymat(lug'at bo'lmasa —AttributeError)
Non-data deskriptor yozishni to'xtatmaydi — qiymat lug'atga tushadi va keyingi o'qishda deskriptorni yashiradi. functools.cached_property aynan shu xossadan foydalanadi.
property setter'siz ham data deskriptor (__set__ bor, lekin xato beradi): property 'x' of 'K' object has no setter.
2.4. Klassdan murojaat: Klass.nom
type.__getattribute__(Klass, nom) — o'xshash algoritm, lekin ikki darajada:
- Metaklass (
type(Klass)) MRO sida data deskriptor →__get__(Klass, type(Klass)) - Klassning o'z MRO si (
Klass.__mro__) da topilgan narsa: deskriptor bo'lsa →__get__(None, Klass), aks holda o'zi - Metaklassdagi non-data deskriptor yoki oddiy atribut
Shuning uchun deskriptorlar obj is None holatini tekshiradi:
| Deskriptor | __get__(None, Klass) |
|---|---|
| Funksiya | Funksiyaning o'zi |
property |
property obyekti |
classmethod |
Klass ga bog'langan metod |
staticmethod |
Ichki funksiya |
| O'zingizniki | Odatda self — tekshirish va hujjat uchun |
Klass.nom = qiymat klass lug'atiga yozadi — deskriptorning __set__ i chaqirilmaydi, deskriptor almashtiriladi (metaklassda deskriptor bo'lmasa).
2.5. Holat qayerda saqlanadi
Deskriptor obyekti klassga tegishli va barcha nusxalar uchun bitta. Nusxaga xos qiymatni saqlashning to'rt yo'li:
| Joy | Kod | Muammo |
|---|---|---|
| Deskriptorning o'zida | self.qiymat = q |
Barcha nusxalar bitta qiymatni bo'lishadi |
WeakKeyDictionary |
self.qiymatlar[obj] = q |
Nusxa xeshlanadigan bo'lishi kerak (dataclass(eq=True) — yo'q); __eq__ bo'yicha teng nusxalar aralashadi |
Nusxa __dict__ i, __set_name__ dagi nom bilan |
obj.__dict__[self.nom] = q |
standart; __slots__ li klassda __dict__ yo'q |
__slots__ dagi yashirin nom |
setattr(obj, "_" + nom, q) |
Slotni alohida e'lon qilish kerak; sekinroq |
Nusxa __dict__ ida deskriptor bilan bir xil nom ishlatish mumkin — deskriptor data bo'lsa, u baribir lug'atdan ustun. Bu getattr rekursiyasini oldini oladi va vars(obj) toza ko'rinadi.
2.6. Tuzoqlar
1. Bitta deskriptor nusxasi ikki nomda:
narx = Musbat()
class Tovar:
sotuv = narx
tannarx = narx # __set_name__ ikki marta — oxirgi nom ("tannarx") qoladiIkkala atribut ham bir joyga yozadi. Har maydon uchun alohida deskriptor nusxasi yarating.
2. __slots__ bilan bir xil nom: ValueError: 'x' in __slots__ conflicts with class variable. Slot o'zi — member_descriptor.
3. Klassga tayinlash deskriptorni almashtiradi: Tovar.narx = 0 — validatsiya butunlay yo'qoladi.
4. __getattr__ deskriptor xatolarini yashiradi: property yoki __get__ ichidagi AttributeError "atribut yo'q" deb talqin qilinadi. Yechim: __getattr__ ni ehtiyot bilan yozing, deskriptor ichida AttributeError ni boshqa istisnoga o'rang.
5. Keyin qo'shilgan deskriptor __set_name__ ni olmaydi (10.10-dars).
2.7. Bog'langan metodlar
Funksiya — non-data deskriptor:
def __get__(self, obj, egasi=None): # function.__get__ — soddalashtirilgan
if obj is None:
return self
return types.MethodType(self, obj)Oqibatlari:
obj.metod is obj.metod→False— har murojaatda yangi bog'langan metod obyekti (==esaTrue)obj.metod.__func__— funksiya,obj.metod.__self__— nusxaobj.metod = boshqa— lug'atga yoziladi va metodni yashiradi (non-data)- Issiq siklda
m = obj.metodni sikldan oldin olish — har iteratsiyada bog'lashni tejaydi
classmethod.__get__ klassni bog'laydi, staticmethod.__get__ funksiyaning o'zini qaytaradi, property — data deskriptor, 3.14 dagi functools.partial — funksiya kabi bog'lanadi.
2.8. Qachon nima
| Vosita | Qachon |
|---|---|
| Oddiy atribut | Standart — eng tez |
property |
Bitta klassdagi bitta hisoblanadigan yoki tekshiriladigan atribut |
functools.cached_property |
Bir marta hisoblanadigan qimmat qiymat (__dict__ kerak) |
| O'z deskriptoringiz | Bir xil mantiq ko'p atribut va ko'p klassda takrorlansa (validatsiya, birliklar, ORM ustunlari) |
__getattr__ |
Dinamik, oldindan noma'lum nomlar (proksi, konfiguratsiya) — faqat zaxira |
__getattribute__ |
Deyarli hech qachon — har murojaatni ushlaydi, rekursiya xavfi |
3. Tez ma'lumotnoma
O'qish: obj.nom
1. type(obj).__mro__ dagi DATA deskriptor → __get__(obj, type(obj))
2. obj.__dict__[nom]
3. type(obj).__mro__ dagi NON-DATA deskriptor → __get__(obj, type(obj))
4. klass atributi
5. AttributeError → __getattr__ (bo'lsa)Yozish: obj.nom = q
data deskriptor → __set__(obj, q) aks holda → obj.__dict__[nom] = q
Klass.nom = q → klass lug'ati (deskriptor almashadi)Klassdan: Klass.nom
deskriptor.__get__(None, Klass)
funksiya → funksiya property → property classmethod → bog'langan staticmethod → funksiyaShablon
class Maydon:
def __set_name__(self, egasi, nom):
self.nom = nom
def __get__(self, obj, egasi=None):
if obj is None:
return self
try:
return obj.__dict__[self.nom]
except KeyError:
raise AttributeError(self.nom) from None
def __set__(self, obj, qiymat):
obj.__dict__[self.nom] = self.tekshir(qiymat)4. Batafsil misollar
Misol 1 — __getattribute__ va __setattr__ ni qayta yozish
"""oqi() / yoz() / klassdan_oqi() — interpretator bilan har xil atribut turlarida solishtirish."""
import functools
TOPILMADI = object()
def _mro_dan(tur, nom):
for klass in tur.__mro__:
if nom in vars(klass):
return vars(klass)[nom]
return TOPILMADI
def _data_mi(attr):
t = type(attr)
return _mro_dan(t, "__set__") is not TOPILMADI or _mro_dan(t, "__delete__") is not TOPILMADI
def _asosiy_oqi(obj, nom):
tur = type(obj)
attr = _mro_dan(tur, nom)
get = _mro_dan(type(attr), "__get__") if attr is not TOPILMADI else TOPILMADI
if get is not TOPILMADI and _data_mi(attr):
return get(attr, obj, tur), "data deskriptor"
lugat = getattr(obj, "__dict__", None)
if lugat is not None and nom in lugat:
return lugat[nom], "nusxa lug'ati"
if get is not TOPILMADI:
return get(attr, obj, tur), "non-data deskriptor"
if attr is not TOPILMADI:
return attr, "klass atributi"
raise AttributeError(f"{tur.__name__!r} object has no attribute {nom!r}")
def oqi(obj, nom):
try:
return _asosiy_oqi(obj, nom)
except AttributeError:
getattr_ = _mro_dan(type(obj), "__getattr__")
if getattr_ is TOPILMADI:
raise
return getattr_(obj, nom), "__getattr__ zaxirasi"
def yoz(obj, nom, qiymat):
attr = _mro_dan(type(obj), nom)
if attr is not TOPILMADI and _data_mi(attr):
set_ = _mro_dan(type(attr), "__set__")
if set_ is TOPILMADI:
raise AttributeError(f"{nom}: __set__ yo'q")
set_(attr, obj, qiymat)
return "data deskriptor __set__"
obj.__dict__[nom] = qiymat
return "nusxa lug'ati"
class Oqiladigan:
"""Faqat __get__ — non-data."""
def __get__(self, obj, egasi=None):
return "non-data qiymati" if obj is not None else self
class Yoziladigan:
"""__get__ + __set__ — data."""
def __set_name__(self, egasi, nom):
self.nom = nom
def __get__(self, obj, egasi=None):
return self if obj is None else obj.__dict__.get(self.nom, "boshlang'ich")
def __set__(self, obj, qiymat):
obj.__dict__[self.nom] = f"<{qiymat}>"
class Namuna:
klass_attr = "klassniki"
soya = "klassniki"
data = Yoziladigan()
nondata = Oqiladigan()
def __init__(self):
self.oddiy = "nusxaniki"
self.soya = "nusxaniki"
def metod(self):
return "metod"
@property
def xossa(self):
return "property"
@functools.cached_property
def qimmat(self):
return "hisoblandi"
@classmethod
def km(cls):
return cls.__name__
@staticmethod
def sm():
return "statik"
def __getattr__(self, nom):
return f"zaxira:{nom}"
print("=== 1. O'qish: oqi() va getattr() ===")
n1, n2 = Namuna(), Namuna()
for nom in ["oddiy", "klass_attr", "soya", "data", "nondata", "metod", "xossa",
"qimmat", "km", "sm", "mavjud_emas"]:
bizniki, manba = oqi(n1, nom)
haqiqiy = getattr(n2, nom)
if callable(bizniki):
bizniki, haqiqiy = bizniki(), haqiqiy()
print(f" {nom:12} {manba:22} {str(bizniki):18} {'=' if bizniki == haqiqiy else '≠'}")
print("\n=== 2. Yozish: yoz() va setattr() ===")
for nom, qiymat in [("data", 5), ("nondata", "yozildi"), ("xossa", 1), ("yangi", 7)]:
try:
manba = yoz(n1, nom, qiymat)
except AttributeError as e:
manba = f"AttributeError"
try:
setattr(n2, nom, qiymat)
haqiqiy = "ok"
except AttributeError as e:
haqiqiy = f"AttributeError: {e}"
print(f" {nom:8} ← {qiymat!r:10} bizniki: {manba:26} haqiqiy: {haqiqiy}")
print(f" yozilgandan keyin: data={n1.data!r}, nondata={n1.nondata!r} ⚠️ non-data lug'at bilan yashirildi")
print("\n=== 3. cached_property — non-data deskriptor + lug'at ===")
n3 = Namuna()
print(f" oldin lug'atda: {'qimmat' in vars(n3)}, o'qish manbasi: {oqi(n3, 'qimmat')[1]}")
print(f" keyin lug'atda: {'qimmat' in vars(n3)}, o'qish manbasi: {oqi(n3, 'qimmat')[1]}")
print("\n=== 4. Klassdan murojaat: __get__(None, Klass) ===")
for nom in ["metod", "xossa", "km", "sm", "data", "nondata", "qimmat"]:
attr = vars(Namuna)[nom]
natija = getattr(Namuna, nom)
print(f" Namuna.{nom:8} lug'atda: {type(attr).__name__:16} → {type(natija).__name__}")Natijaning muhim qismi:
=== 1. O'qish: oqi() va getattr() ===
oddiy nusxa lug'ati nusxaniki =
klass_attr klass atributi klassniki =
soya nusxa lug'ati nusxaniki =
data data deskriptor boshlang'ich =
nondata non-data deskriptor non-data qiymati =
metod non-data deskriptor metod =
xossa data deskriptor property =
qimmat non-data deskriptor hisoblandi =
km non-data deskriptor Namuna =
sm non-data deskriptor statik =
mavjud_emas __getattr__ zaxirasi zaxira:mavjud_emas =
=== 2. Yozish: yoz() va setattr() ===
data ← 5 bizniki: data deskriptor __set__ haqiqiy: ok
nondata ← 'yozildi' bizniki: nusxa lug'ati haqiqiy: ok
xossa ← 1 bizniki: AttributeError haqiqiy: AttributeError: property 'xossa' of 'Namuna' object has no setter
yangi ← 7 bizniki: nusxa lug'ati haqiqiy: ok
yozilgandan keyin: data='<5>', nondata='yozildi' ⚠️ non-data lug'at bilan yashirildi
=== 3. cached_property — non-data deskriptor + lug'at ===
oldin lug'atda: False, o'qish manbasi: non-data deskriptor
keyin lug'atda: True, o'qish manbasi: nusxa lug'ati
=== 4. Klassdan murojaat: __get__(None, Klass) ===
Namuna.metod lug'atda: function → function
Namuna.xossa lug'atda: property → property
Namuna.km lug'atda: classmethod → method
Namuna.sm lug'atda: staticmethod → function
Namuna.data lug'atda: Yoziladigan → Yoziladigan
Namuna.nondata lug'atda: Oqiladigan → Oqiladigan
Namuna.qimmat lug'atda: cached_property → cached_propertyNima ko'rsatdi: 2.2, 2.3, 2.4-bo'limlar — 40 qatorlik oqi() va yoz() interpretator bilan barcha atribut turlarida bir xil natija berdi.
Misol 2 — Holat qayerda saqlanadi va tuzoqlar
"""To'rt saqlash usuli; bir deskriptor ikki nomda; klassga tayinlash; slots to'qnashuvi; __getattr__ yashirgan xato."""
import weakref
from dataclasses import dataclass
class DeskriptordaSaqlovchi:
def __get__(self, obj, egasi=None):
return self if obj is None else getattr(self, "qiymat", None)
def __set__(self, obj, qiymat):
self.qiymat = qiymat # ❌ hamma nusxa uchun bitta
class ZaifLugatda:
def __init__(self):
self.qiymatlar = weakref.WeakKeyDictionary()
def __get__(self, obj, egasi=None):
return self if obj is None else self.qiymatlar.get(obj)
def __set__(self, obj, qiymat):
self.qiymatlar[obj] = qiymat
class NusxaLugatida:
def __set_name__(self, egasi, nom):
self.nom = nom
def __get__(self, obj, egasi=None):
return self if obj is None else obj.__dict__.get(self.nom)
def __set__(self, obj, qiymat):
obj.__dict__[self.nom] = qiymat
class SlotdaSaqlovchi:
def __set_name__(self, egasi, nom):
self.slot = "_" + nom
def __get__(self, obj, egasi=None):
return self if obj is None else getattr(obj, self.slot, None)
def __set__(self, obj, qiymat):
setattr(obj, self.slot, qiymat)
print("=== 1. To'rt saqlash usuli ===")
class A:
narx = DeskriptordaSaqlovchi()
class B:
narx = ZaifLugatda()
class C:
narx = NusxaLugatida()
class D:
__slots__ = ("_narx",)
narx = SlotdaSaqlovchi()
for klass in (A, B, C, D):
x, y = klass(), klass()
x.narx, y.narx = 100, 200
ichki = vars(x) if hasattr(x, "__dict__") else {"_narx (slot)": x._narx}
print(f" {klass.narx.__class__.__name__:22} x.narx={x.narx}, y.narx={y.narx}, x ichida: {ichki}")
@dataclass
class Hisob:
narx = ZaifLugatda()
raqam: str = "8600"
try:
Hisob().narx = 5
except TypeError as e:
print(f" ⚠️ WeakKeyDictionary + dataclass(eq=True): TypeError: {e}")
print("\n=== 2. ⚠️ Bitta deskriptor nusxasi ikki nomda ===")
class Nomli(NusxaLugatida):
def __set_name__(self, egasi, nom):
print(f" __set_name__({egasi.__name__}, {nom!r})")
super().__set_name__(egasi, nom)
umumiy = Nomli()
class Tovar:
sotuv = umumiy
tannarx = umumiy
t = Tovar()
t.sotuv = 15_000
t.tannarx = 9_000
print(f" sotuv={t.sotuv}, tannarx={t.tannarx}, lug'at: {vars(t)} ⚠️ ikkalasi 'tannarx' ga yozdi")
print("\n=== 3. ⚠️ Klassga tayinlash deskriptorni almashtiradi ===")
class Musbat(NusxaLugatida):
def __set__(self, obj, qiymat):
if qiymat < 0:
raise ValueError(f"{self.nom} manfiy bo'lmasin")
super().__set__(obj, qiymat)
class Mahsulot:
narx = Musbat()
try:
Mahsulot().narx = -5
except ValueError as e:
print(f" oldin: ValueError: {e}")
Mahsulot.narx = 0
m = Mahsulot()
m.narx = -5
print(f" Mahsulot.narx = 0 dan keyin: m.narx = {m.narx}, klass lug'atida: {type(vars(Mahsulot)['narx']).__name__} ⚠️ validatsiya yo'qoldi")
print("\n=== 4. ⚠️ __slots__ bilan bir xil nom ===")
try:
class Nuqta:
__slots__ = ("x",)
x = Musbat()
except ValueError as e:
print(f" ValueError: {e}")
class Nuqta2:
__slots__ = ("x",)
print(f" slotning o'zi deskriptor: {type(vars(Nuqta2)['x']).__name__}, data: {hasattr(type(vars(Nuqta2)['x']), '__set__')}")
print("\n=== 5. ⚠️ __getattr__ deskriptor ichidagi xatoni yashiradi ===")
class Konfig:
def __init__(self, malumot):
self._malumot = malumot
def __getattr__(self, nom):
return self._malumot.get(nom, "sukut")
@property
def ulanish(self):
return f"{self._malumot['host']}:{self._malumot['port'].strip()}"
class XavfsizKonfig(Konfig):
@property
def ulanish(self):
try:
return f"{self._malumot['host']}:{self._malumot['port'].strip()}"
except AttributeError as e:
raise RuntimeError(f"ulanish hisoblanmadi: {e}") from e
malumot = {"host": "db.local", "port": 5432} # port — int, .strip() yo'q
print(f" Konfig.ulanish → {Konfig(malumot).ulanish!r} ⚠️ AttributeError yashirindi")
try:
XavfsizKonfig(malumot).ulanish
except RuntimeError as e:
print(f" XavfsizKonfig.ulanish → RuntimeError: {e}")Natijaning muhim qismi:
=== 1. To'rt saqlash usuli ===
DeskriptordaSaqlovchi x.narx=200, y.narx=200, x ichida: {}
ZaifLugatda x.narx=100, y.narx=200, x ichida: {}
NusxaLugatida x.narx=100, y.narx=200, x ichida: {'narx': 100}
SlotdaSaqlovchi x.narx=100, y.narx=200, x ichida: {'_narx (slot)': 100}
⚠️ WeakKeyDictionary + dataclass(eq=True): TypeError: cannot use 'weakref.ReferenceType' as a dict key (unhashable type: 'Hisob')
=== 2. ⚠️ Bitta deskriptor nusxasi ikki nomda ===
__set_name__(Tovar, 'sotuv')
__set_name__(Tovar, 'tannarx')
sotuv=9000, tannarx=9000, lug'at: {'tannarx': 9000} ⚠️ ikkalasi 'tannarx' ga yozdi
=== 3. ⚠️ Klassga tayinlash deskriptorni almashtiradi ===
oldin: ValueError: narx manfiy bo'lmasin
Mahsulot.narx = 0 dan keyin: m.narx = -5, klass lug'atida: int ⚠️ validatsiya yo'qoldi
=== 4. ⚠️ __slots__ bilan bir xil nom ===
ValueError: 'x' in __slots__ conflicts with class variable
slotning o'zi deskriptor: member_descriptor, data: True
=== 5. ⚠️ __getattr__ deskriptor ichidagi xatoni yashiradi ===
Konfig.ulanish → 'sukut' ⚠️ AttributeError yashirindi
XavfsizKonfig.ulanish → RuntimeError: ulanish hisoblanmadi: 'int' object has no attribute 'strip'Nima ko'rsatdi: 2.5, 2.6-bo'limlar.
Misol 3 — O'rnatilgan deskriptorlarni sof Python'da yozish
"""Funksiya bog'lanishi; o'z property, classmethod, staticmethod imiz; asli bilan solishtirish."""
import types
print("=== 1. Bog'langan metodlar ===")
class Hisob:
def __init__(self, balans):
self.balans = balans
def qoldiq(self):
return self.balans
h = Hisob(700)
funksiya = vars(Hisob)["qoldiq"]
print(f" lug'atda: {type(funksiya).__name__}, h.qoldiq: {type(h.qoldiq).__name__}")
print(f" h.qoldiq is h.qoldiq: {h.qoldiq is h.qoldiq}, ==: {h.qoldiq == h.qoldiq}")
bogl = funksiya.__get__(h, Hisob)
print(f" funksiya.__get__(h) → __func__ is funksiya: {bogl.__func__ is funksiya}, __self__ is h: {bogl.__self__ is h}, chaqiruv: {bogl()}")
qolda = types.MethodType(funksiya, h)
print(f" types.MethodType(funksiya, h)() = {qolda()}")
h.qoldiq = lambda: "nusxada almashtirildi"
print(f" h.qoldiq = lambda → {h.qoldiq()} (funksiya non-data)")
class MeningProperty:
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.fget, self.fset, self.fdel = fget, fset, fdel
self.__doc__ = doc if doc is not None else getattr(fget, "__doc__", None)
self.nom = getattr(fget, "__name__", "?")
def __set_name__(self, egasi, nom):
self.nom = nom
def __get__(self, obj, egasi=None):
if obj is None:
return self
if self.fget is None:
raise AttributeError(f"property {self.nom!r} of {type(obj).__qualname__!r} object has no getter")
return self.fget(obj)
def __set__(self, obj, qiymat):
if self.fset is None:
raise AttributeError(f"property {self.nom!r} of {type(obj).__qualname__!r} object has no setter")
self.fset(obj, qiymat)
def __delete__(self, obj):
if self.fdel is None:
raise AttributeError(f"property {self.nom!r} of {type(obj).__qualname__!r} object has no deleter")
self.fdel(obj)
def setter(self, fset):
return type(self)(self.fget, fset, self.fdel, self.__doc__)
def deleter(self, fdel):
return type(self)(self.fget, self.fset, fdel, self.__doc__)
class MeningClassmethod:
def __init__(self, f):
self.f = f
def __get__(self, obj, egasi=None):
return types.MethodType(self.f, egasi if egasi is not None else type(obj))
class MeningStaticmethod:
def __init__(self, f):
self.f = f
def __get__(self, obj, egasi=None):
return self.f
def harorat_klassi(prop, cm, sm):
class Harorat:
def __init__(self, selsiy):
self._c = selsiy
@prop
def selsiy(self):
"""Selsiy bo'yicha harorat."""
return self._c
@selsiy.setter
def selsiy(self, qiymat):
if qiymat < -273.15:
raise ValueError("absolyut noldan past")
self._c = qiymat
@prop
def farengeyt(self):
return self._c * 9 / 5 + 32
@cm
def muzlash(cls):
return cls(0)
@sm
def kelvinga(c):
return c + 273.15
return Harorat
print("\n=== 2. O'rnatilgan va o'zimizning deskriptorlar ===")
natijalar = {}
for nom, (prop, cm, sm) in {"o'rnatilgan": (property, classmethod, staticmethod),
"bizniki": (MeningProperty, MeningClassmethod, MeningStaticmethod)}.items():
H = harorat_klassi(prop, cm, sm)
t = H(25)
qadamlar = [t.selsiy, t.farengeyt]
t.selsiy = 100
qadamlar.append(t.farengeyt)
for amal in (lambda: setattr(t, "selsiy", -300), lambda: setattr(t, "farengeyt", 0), lambda: delattr(t, "selsiy")):
try:
amal()
qadamlar.append("ok")
except (ValueError, AttributeError) as e:
qadamlar.append(f"{type(e).__name__}: {e}")
qadamlar += [H.muzlash().selsiy, t.muzlash().selsiy, H.kelvinga(0), t.kelvinga(10), H.selsiy.__doc__,
type(vars(H)["selsiy"]).__name__]
natijalar[nom] = qadamlar
nomlar = ["selsiy", "farengeyt", "100°C → F", "selsiy = -300", "farengeyt = 0", "del selsiy",
"H.muzlash()", "t.muzlash()", "H.kelvinga(0)", "t.kelvinga(10)", "__doc__"]
for i, nom in enumerate(nomlar):
a, b = natijalar["o'rnatilgan"][i], natijalar["bizniki"][i]
print(f" {nom:15} {'=' if a == b else '≠'} {a}")
asli_tur, bizniki_tur = natijalar["o'rnatilgan"][-1], natijalar["bizniki"][-1]
print(f" lug'atdagi tur: {asli_tur} va {bizniki_tur} — farq faqat shu")Natijaning muhim qismi:
=== 1. Bog'langan metodlar ===
lug'atda: function, h.qoldiq: method
h.qoldiq is h.qoldiq: False, ==: True
funksiya.__get__(h) → __func__ is funksiya: True, __self__ is h: True, chaqiruv: 700
types.MethodType(funksiya, h)() = 700
h.qoldiq = lambda → nusxada almashtirildi (funksiya non-data)
=== 2. O'rnatilgan va o'zimizning deskriptorlar ===
selsiy = 25
farengeyt = 77.0
100°C → F = 212.0
selsiy = -300 = ValueError: absolyut noldan past
farengeyt = 0 = AttributeError: property 'farengeyt' of 'harorat_klassi.<locals>.Harorat' object has no setter
del selsiy = AttributeError: property 'selsiy' of 'harorat_klassi.<locals>.Harorat' object has no deleter
H.muzlash() = 0
t.muzlash() = 0
H.kelvinga(0) = 273.15
t.kelvinga(10) = 283.15
__doc__ = Selsiy bo'yicha harorat.
lug'atdagi tur: property va MeningProperty — farq faqat shuNima ko'rsatdi: 2.4, 2.7-bo'limlar — property, classmethod va staticmethod o'n-o'n besh qatorlik sof Python deskriptorlari bilan xuddi shunday ishlaydi, xato xabarlarigacha.
Misol 4 — Amaliy: maydonlar asosidagi validatsiya freymvorki
Django modellari va attrs uslubidagi kichik freymvork: har maydon — deskriptor, Model bazaviy klassi __init_subclass__ bilan maydonlarni yig'adi, avtomatik __init__ va __repr__ beradi, xatolarni bitta hisobotga jamlaydi.
"""Maydon deskriptorlari + __set_name__ + __init_subclass__: tekshiruv, sukut qiymat, hosila maydon, meros."""
import re
class ValidatsiyaXatosi(ValueError):
def __init__(self, klass, xatolar):
self.xatolar = xatolar
qatorlar = "; ".join(f"{m}: {x}" for m, x in xatolar.items())
super().__init__(f"{klass}: {qatorlar}")
YOQ = object()
class Maydon:
def __init__(self, *, sukut=YOQ, izoh=""):
self.sukut, self.izoh = sukut, izoh
def __set_name__(self, egasi, nom):
self.nom = nom
self.egasi = egasi.__name__
def __get__(self, obj, egasi=None):
if obj is None:
return self
try:
return obj.__dict__[self.nom]
except KeyError:
raise AttributeError(f"{self.egasi}.{self.nom} hali o'rnatilmagan") from None
def __set__(self, obj, qiymat):
obj.__dict__[self.nom] = self.tekshir(qiymat)
def tekshir(self, qiymat):
return qiymat
def __repr__(self):
return f"{type(self).__name__}({self.nom})"
class Butun(Maydon):
def __init__(self, *, min=None, max=None, **kw):
super().__init__(**kw)
self.min, self.max = min, max
def tekshir(self, qiymat):
if isinstance(qiymat, bool) or not isinstance(qiymat, int):
raise TypeError(f"butun son kerak, {type(qiymat).__name__} berildi")
if self.min is not None and qiymat < self.min:
raise ValueError(f"{self.min} dan kichik: {qiymat}")
if self.max is not None and qiymat > self.max:
raise ValueError(f"{self.max} dan katta: {qiymat}")
return qiymat
class Satr(Maydon):
def __init__(self, *, max_uzunlik=None, naqsh=None, **kw):
super().__init__(**kw)
self.max_uzunlik = max_uzunlik
self.naqsh = re.compile(naqsh) if naqsh else None
def tekshir(self, qiymat):
if not isinstance(qiymat, str):
raise TypeError(f"satr kerak, {type(qiymat).__name__} berildi")
qiymat = qiymat.strip()
if self.max_uzunlik and len(qiymat) > self.max_uzunlik:
raise ValueError(f"{self.max_uzunlik} belgidan uzun")
if self.naqsh and not self.naqsh.fullmatch(qiymat):
raise ValueError(f"format noto'g'ri: {qiymat!r}")
return qiymat
class Tanlov(Maydon):
def __init__(self, *variantlar, **kw):
super().__init__(**kw)
self.variantlar = variantlar
def tekshir(self, qiymat):
if qiymat not in self.variantlar:
raise ValueError(f"{qiymat!r} — ruxsat: {', '.join(map(str, self.variantlar))}")
return qiymat
class Hosila:
"""Non-data: faqat o'qiladi, har safar hisoblanadi."""
def __init__(self, funksiya):
self.funksiya = funksiya
self.__doc__ = funksiya.__doc__
def __set_name__(self, egasi, nom):
self.nom = nom
def __get__(self, obj, egasi=None):
return self if obj is None else self.funksiya(obj)
def __set__(self, obj, qiymat):
raise AttributeError(f"{self.nom} hosila maydon — o'rnatib bo'lmaydi")
class Model:
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
maydonlar = {}
for klass in reversed(cls.__mro__):
for nom, qiymat in vars(klass).items():
if isinstance(qiymat, Maydon):
maydonlar[nom] = qiymat
cls._maydonlar = maydonlar
def __init__(self, **qiymatlar):
xatolar = {}
noma_lum = set(qiymatlar) - set(self._maydonlar)
for nom in sorted(noma_lum):
xatolar[nom] = "noma'lum maydon"
for nom, maydon in self._maydonlar.items():
qiymat = qiymatlar.get(nom, maydon.sukut)
if qiymat is YOQ:
xatolar[nom] = "majburiy"
continue
try:
setattr(self, nom, qiymat)
except (TypeError, ValueError) as e:
xatolar[nom] = str(e)
if xatolar:
raise ValidatsiyaXatosi(type(self).__name__, xatolar)
def __repr__(self):
qism = ", ".join(f"{n}={getattr(self, n)!r}" for n in self._maydonlar)
return f"{type(self).__name__}({qism})"
@classmethod
def sxema(cls):
return {n: f"{type(m).__name__}" + (f" = {m.sukut!r}" if m.sukut is not YOQ else "")
for n, m in cls._maydonlar.items()}
class Foydalanuvchi(Model):
ism = Satr(max_uzunlik=30)
email = Satr(naqsh=r"[\w.]+@[\w.]+\.\w+")
yosh = Butun(min=14, max=120)
rol = Tanlov("talaba", "o'qituvchi", "admin", sukut="talaba")
@Hosila
def tavsif(self):
"""Qisqa tavsif."""
return f"{self.ism} ({self.rol}, {self.yosh} yosh)"
class Oqituvchi(Foydalanuvchi):
fan = Satr(max_uzunlik=40)
tajriba = Butun(min=0, sukut=0)
print("=== 1. Sxema va meros ===")
print(f" Foydalanuvchi: {Foydalanuvchi.sxema()}")
print(f" Oqituvchi: {list(Oqituvchi.sxema())}")
print(f" Foydalanuvchi.yosh (klassdan): {Foydalanuvchi.yosh!r}, min={Foydalanuvchi.yosh.min}")
print("\n=== 2. To'g'ri obyektlar ===")
f = Foydalanuvchi(ism=" Dilnoza ", email="dilnoza@wisar.uz", yosh=21)
o = Oqituvchi(ism="Aziz", email="aziz@wisar.uz", yosh=35, rol="o'qituvchi", fan="Python", tajriba=8)
print(f" {f}")
print(f" {o}")
print(f" hosila: {f.tavsif!r}, lug'at: {vars(f)}")
print("\n=== 3. Barcha xatolar bitta hisobotda ===")
try:
Foydalanuvchi(ism="A" * 40, email="noto'g'ri", yosh="yigirma", rol="direktor", telefon="+998")
except ValidatsiyaXatosi as e:
for maydon, xato in e.xatolar.items():
print(f" {maydon:8} → {xato}")
try:
Oqituvchi(ism="Bobur", email="b@w.uz")
except ValidatsiyaXatosi as e:
print(f" majburiylar: {e}")
print("\n=== 4. O'rnatishda ham tekshiruv ===")
for maydon, qiymat in [("yosh", 13), ("yosh", True), ("rol", "admin"), ("tavsif", "yangi")]:
try:
setattr(f, maydon, qiymat)
print(f" f.{maydon} = {qiymat!r} → ✅ {getattr(f, maydon)!r}")
except (TypeError, ValueError, AttributeError) as e:
print(f" f.{maydon} = {qiymat!r} → ❌ {type(e).__name__}: {e}")
print(f" {f.tavsif}")Natijaning muhim qismi:
=== 1. Sxema va meros ===
Foydalanuvchi: {'ism': 'Satr', 'email': 'Satr', 'yosh': 'Butun', 'rol': "Tanlov = 'talaba'"}
Oqituvchi: ['ism', 'email', 'yosh', 'rol', 'fan', 'tajriba']
Foydalanuvchi.yosh (klassdan): Butun(yosh), min=14
=== 2. To'g'ri obyektlar ===
Foydalanuvchi(ism='Dilnoza', email='dilnoza@wisar.uz', yosh=21, rol='talaba')
Oqituvchi(ism='Aziz', email='aziz@wisar.uz', yosh=35, rol="o'qituvchi", fan='Python', tajriba=8)
hosila: 'Dilnoza (talaba, 21 yosh)', lug'at: {'ism': 'Dilnoza', 'email': 'dilnoza@wisar.uz', 'yosh': 21, 'rol': 'talaba'}
=== 3. Barcha xatolar bitta hisobotda ===
telefon → noma'lum maydon
ism → 30 belgidan uzun
email → format noto'g'ri: "noto'g'ri"
yosh → butun son kerak, str berildi
rol → 'direktor' — ruxsat: talaba, o'qituvchi, admin
majburiylar: Oqituvchi: yosh: majburiy; fan: majburiy
=== 4. O'rnatishda ham tekshiruv ===
f.yosh = 13 → ❌ ValueError: 14 dan kichik: 13
f.yosh = True → ❌ TypeError: butun son kerak, bool berildi
f.rol = 'admin' → ✅ 'admin'
f.tavsif = 'yangi' → ❌ AttributeError: tavsif hosila maydon — o'rnatib bo'lmaydi
Dilnoza (admin, 21 yosh)Nima ko'rsatdi: 2.2, 2.5, 2.8-bo'limlar — maydon deskriptorlari holatni nusxa lug'atida bir xil nom bilan saqlaydi, __set_name__ ularga nom beradi, __init_subclass__ meros bo'yicha barcha maydonlarni yig'adi; Hosila esa __set__ bilan data deskriptorga aylanib, yozishni taqiqlaydi.
5. To'g'ri va noto'g'ri tushunishlar
| Noto'g'ri fikr | To'g'risi |
|---|---|
| "Nusxa lug'ati har doim birinchi tekshiriladi" | Data deskriptor lug'atdan oldin |
| "Deskriptor nusxa atributi bo'lsa ham ishlaydi" | Faqat klass atributi sifatida |
| "Deskriptor obyekti har nusxa uchun alohida" | Klass uchun bitta — holatni nusxada saqlang |
"Setter'siz property — non-data deskriptor" |
Data deskriptor, __set__ xato beradi |
"Klass.narx = 0 deskriptorning __set__ ini chaqiradi" |
Deskriptorni almashtiradi |
"obj.metod is obj.metod" |
False — har murojaatda yangi bog'langan metod |
"__getattr__ faqat haqiqatan yo'q atributlar uchun" |
Deskriptor ichidagi AttributeError da ham |
"O'z deskriptorim property dan tezroq" |
Python'da yozilgan deskriptor sekinroq — C'dagi property tezroq |
6. Keng tarqalgan xatolar va yechimlari
1. Qiymatni deskriptorning o'zida saqlash
def __set__(self, obj, q):
self.qiymat = q # ❌ hamma nusxa bir qiymat
def __set__(self, obj, q):
obj.__dict__[self.nom] = q # ✅2. getattr/setattr bilan cheksiz rekursiya
def __set_name__(self, egasi, nom): self.nom = nom
def __get__(self, obj, egasi=None):
return getattr(obj, self.nom) # ❌ yana o'sha deskriptor → RecursionError
return obj.__dict__[self.nom] # ✅ lug'atga to'g'ridan-to'g'ri3. obj is None ni tekshirmaslik
def __get__(self, obj, egasi=None):
return obj.__dict__[self.nom] # ❌ Klass.maydon → AttributeError: 'NoneType'
if obj is None:
return self # ✅ introspeksiya va hujjat uchun4. Bitta deskriptorni ikki atributga berish
musbat = Musbat()
class Tovar:
narx = musbat
chegirma = musbat # ❌ ikkalasi bitta nom bilan
class Tovar:
narx = Musbat()
chegirma = Musbat() # ✅5. Deskriptor ichida AttributeError + __getattr__
@property
def toliq_nom(self):
return self.ism.title() # ism None bo'lsa AttributeError → __getattr__ → jim xato Property ichida AttributeError ni boshqa istisnoga o'rang yoki __getattr__ da noma'lum nomlar uchun AttributeError ko'taring.
6. __slots__ va bir xil nomli deskriptor
class Nuqta:
__slots__ = ("x",)
x = Musbat() # ❌ ValueError: conflicts with class variable
class Nuqta:
__slots__ = ("_x",)
x = Musbat(slot="_x") # ✅ deskriptor yashirin slotga yozadi7. WeakKeyDictionary ga xeshlanmaydigan nusxalar
@dataclass
class Hisob:
narx = ZaifLugatli() # ❌ dataclass(eq=True) → __hash__ = None → TypeError Nusxa lug'atida saqlang yoki weakref.WeakKeyDictionary o'rniga id(obj) + weakref.finalize (murakkab) — odatda birinchi yo'l yetadi.
8. Keraksiz __getattribute__
def __getattribute__(self, nom):
jurnal(nom)
return super().__getattribute__(nom) # ⚠️ har murojaat sekinlashadi, self.jurnal → rekursiya xavfi Ma'lum atributlar uchun deskriptor yoki property, noma'lumlar uchun __getattr__.
7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 8.4, 8.15, 8.16, 8.19-darslar (o'tilgan): metodlar,
property,classmethod/staticmethod,__slots__— hammasi deskriptor - 10.10-dars (o'tilgan): keyin qo'shilgan deskriptor va
__set_name__;__call__li klassga__get__ - 10.13-dars (o'tilgan):
partial3.14 da deskriptor - 10.15–10.16-darslar (o'tilgan): ma'lumot modeli, turda qidirish, obyekt yaratilishi
- 12-qism: CPython ichki tuzilishi —
member_descriptorva tur slotlari - 13-qism: tur annotatsiyalari,
pydanticvadataclass_transform— deskriptorli modellar uchun tekshiruv - 21, 23-qismlar: Django
models.Field, SQLAlchemyMapped[...]— deskriptorlar ustiga qurilgan ORM'lar - 20-qism: FastAPI'dagi Pydantic modellari va maydon validatsiyasi
8. Eng yaxshi amaliyotlar
Avval oddiy atribut, keyin
property, keyin deskriptor. Deskriptor — mantiq ko'p joyda takrorlanganda.Holatni nusxa
__dict__ida,__set_name__dan olingan nom bilan saqlang.__get__daobj is None→self.Har atribut uchun alohida deskriptor nusxasi.
Validatsiya uchun data deskriptor, kesh uchun non-data.
Deskriptor ichidagi
AttributeErrorni ehtiyot qiling — ayniqsa__getattr__bor klasslarda.__getattribute__ni yuklamang, agar juda aniq sabab bo'lmasa.Issiq siklda bog'langan metodni oldindan oling —
qosh = royxat.append.
9. Amaliy topshiriq
Vazifa 1: Natijani bashorat qiling
class Data:
def __set_name__(self, egasi, nom):
self.nom = "_" + nom
def __get__(self, obj, egasi=None):
if obj is None:
return "klassdan"
return getattr(obj, self.nom, "yo'q")
def __set__(self, obj, qiymat):
setattr(obj, self.nom, qiymat * 2)
class NonData:
def __get__(self, obj, egasi=None):
return "nondata"
class K:
d = Data()
n = NonData()
def m(self):
return "metod"
1. k = K()
print(k.d, K.d)
2. k = K()
k.d = 5
print(k.d, k.__dict__)
3. k = K()
print(k.n)
k.n = "o'zim"
print(k.n)
4. k = K()
k.__dict__["d"] = "lug'at"
print(k.d)
5. k = K()
print(k.m is k.m, k.m == k.m)
6. k = K()
k.m = lambda: "almashtirildi"
print(k.m())
7. print(type(K.__dict__["m"]).__name__, type(K().m).__name__)
8. class P:
@property
def x(self):
return 1
try:
P().x = 2
except AttributeError:
print("AttributeError")
9. class G:
def __getattr__(self, n):
return "zaxira"
@property
def p(self):
raise AttributeError("ichki")
print(G().p)
10. class S:
__slots__ = ("a",)
print(type(S.__dict__["a"]).__name__)
11. class T:
a = Data()
b = a
t = T()
t.a = 1
print(t.b, t.__dict__)
12. K.d = 1
print(K().d, K.__dict__["d"])Javoblar
yo'q klassdan—_dhali yo'q; klassdanobj is None10 {'_d': 10}—__set__ikkiga ko'paytirdinondatava keyingi qatordao'zim— non-data lug'at bilan yashirildiyo'q— data deskriptor lug'atdagiddan ustun,_desa yo'qFalse Truealmashtirildi— funksiya non-data deskriptorfunction methodAttributeError— setter yo'qzaxira— property ichidagiAttributeError__getattr__ga o'tdimember_descriptor2 {'_b': 2}—__set_name__ikki marta, oxirgi nomb;t.a = 1ham_bga yozdi1 1— klassga tayinlash deskriptorni almashtirdi
Vazifa 2: Xatolarni tuzating
1. class Musbat:
def __set_name__(self, egasi, nom):
self.nom = nom
def __get__(self, obj, egasi=None):
return getattr(obj, self.nom)
def __set__(self, obj, q):
if q < 0:
raise ValueError
setattr(obj, self.nom, q)
2. class Kesh:
def __init__(self, f):
self.f = f
self.qiymat = None
def __get__(self, obj, egasi=None):
if self.qiymat is None:
self.qiymat = self.f(obj)
return self.qiymat
3. class Nuqta:
__slots__ = ("x", "y")
x = Musbat()
y = Musbat()
4. class Sozlama:
def __getattr__(self, nom):
return os.environ.get(nom.upper(), "")
@property
def baza_url(self):
return self.malumot["baza"].rstrip("/")
5. maydon = Satr(max_uzunlik=50)
class Maqola:
sarlavha = maydon
tavsif = maydonJavoblar
1. class Musbat:
def __set_name__(self, egasi, nom):
self.nom = nom
def __get__(self, obj, egasi=None):
if obj is None:
return self
return obj.__dict__[self.nom] # getattr — cheksiz rekursiya edi
def __set__(self, obj, q):
if q < 0:
raise ValueError(f"{self.nom} manfiy")
obj.__dict__[self.nom] = q
2. class Kesh: # functools.cached_property ning o'zi
def __init__(self, f):
self.f = f
def __set_name__(self, egasi, nom):
self.nom = nom
def __get__(self, obj, egasi=None):
if obj is None:
return self
qiymat = self.f(obj)
obj.__dict__[self.nom] = qiymat # non-data: keyingi o'qish lug'atdan
return qiymat
3. class Musbat:
def __init__(self, slot):
self.slot = slot
def __get__(self, obj, egasi=None):
return self if obj is None else getattr(obj, self.slot)
def __set__(self, obj, q):
if q < 0:
raise ValueError
setattr(obj, self.slot, q)
class Nuqta:
__slots__ = ("_x", "_y")
x = Musbat("_x")
y = Musbat("_y")
4. class Sozlama:
def __getattr__(self, nom):
if nom.startswith("_") or nom == "malumot":
raise AttributeError(nom) # ichki nomlar zaxiraga tushmasin
return os.environ.get(nom.upper(), "")
@property
def baza_url(self):
try:
return self.malumot["baza"].rstrip("/")
except AttributeError as e:
raise RuntimeError(f"baza_url: {e}") from e
5. class Maqola:
sarlavha = Satr(max_uzunlik=50) # har maydonga alohida nusxa
tavsif = Satr(max_uzunlik=50)Vazifa 3: klassdan_oqi ni yozing
Misol 1 dagi oqi() ning klass darajasidagi juftini yozing — klassdan_oqi(Klass, nom):
- Metaklass MRO sidagi data deskriptorlar
- Klassning o'z MRO sidagi deskriptorlar uchun
__get__(None, Klass) - Metaklassdagi non-data deskriptorlar va oddiy atributlar
typeva o'zingizning metaklassingiz (metaklassdapropertybilan) ustidagetattrbilan solishtiring
Vazifa 4: Birlikli maydonlar
Birlikli(asosiy="m", ruxsat=("m", "km", "sm")) deskriptori yozing:
obj.masofa = (5, "km")— asosiy birlikka o'girib saqlasinobj.masofa— asosiy birlikdagi son;Klass.masofa.qanday(obj, "sm")— boshqa birlikda- Noma'lum birlik —
ValueError __slots__li va__dict__li klasslarda ishlasin (saqlash joyini avtomatik tanlasin)- Misol 4 dagi
Modelbilan birlashtiring
Vazifa 5: Kuzatiladigan atributlar
Kuzatiladigan() deskriptori yozing:
- Qiymat o'zgarganda obyektga ulangan kuzatuvchilar chaqirilsin:
obj.kuzat("narx", f)→f(eski, yangi) - Kuzatuvchilar obyektni tirik ushlamasin (
weakref.WeakMethod— 10.12-dars) with obj.jim():bloki ichida xabarnomalar to'plansin va blok oxirida bir marta yuborilsin- Qiymat o'zgarmasa (
eski == yangi) — xabar yuborilmasin
Vazifa 6: ORM ustunlari
Kichik ORM yozing:
Ustun(tur, asosiy_kalit=False)deskriptori vaJadvalbazaviy klassi (__init_subclass__)Klass.ustun— klassdan murojaatda 10.14-darsdagiIfodaobyektini qaytarsin:Foydalanuvchi.yosh >= 18→ SQL ifodaobj.ustun— nusxadan murojaatda qiymat, o'zgartirilgan ustunlarobj._ozgarganto'plamiga tushsinJadval.yarat_sql()—CREATE TABLE,obj.saqla_sql()— faqat o'zgargan ustunlar bilanUPDATEsqlite3bilan end-to-end test
Vazifa 7: O'ylash
Python'da property, metodlar, classmethod, staticmethod, __slots__ va super() bitta deskriptor protokoliga qurilgan. C# da property'lar til sintaksisi, Java'da getter/setter konventsiyasi, JavaScript'da Object.defineProperty bilan "accessor property", Kotlin'da by bilan delegatlangan xossalar bor. Python'ning yagona protokol yondashuvi qanday afzallik va kamchiliklarga ega?
Javob
Qisqa javob: Python atributga murojaatni bitta kengaytiriladigan protokolga aylantirgan: tilning o'zi (property, metodlar) va foydalanuvchi kutubxonalari (ORM ustunlari, validatsiya maydonlari) bir xil mexanizmdan foydalanadi. Bu kuchli va izchil — lekin atribut qayerdan kelayotgani ko'rinmaydi, Python'da yozilgan deskriptorlar sekin va tuzoqlar (holat saqlash, __getattr__ bilan aralashuv) protokolni ichidan bilishni talab qiladi.
1. Python'da nega bitta protokol
Python 2.2 dagi "turlar va klasslarni birlashtirish" (PEP 252, PEP 253) bilan deskriptorlar kiritildi. Maqsad — o'rnatilgan turlar va foydalanuvchi klasslari atributlarni bir xil qoidalar bilan qidirishi. Natijada:
- metod — oddiy funksiya +
__get__ classmethod,staticmethod,property— maxsus sintaksis emas, kutubxona darajasidagi klasslar__slots__— C darajasidagimember_descriptorsuper()— bog'lanishni boshqaradigan obyekt
Bu darsda uchalasini ham 10–15 qatorlik sof Python bilan yozdik (Misol 3).
2. Boshqa tillar
C# — property til sintaksisi: public int Yosh { get; set; }. Kompilyator get_Yosh/set_Yosh metodlarini yaratadi. Tez, IDE va refleksiya uchun aniq. Lekin bir xil mantiqni ko'p xossaga qo'llash (masalan, har xossada validatsiya) uchun atributlar + kod generatsiyasi yoki AOP vositalari kerak.
Java — til darajasida property yo'q: getYosh()/setYosh() konventsiyasi, JavaBeans spetsifikatsiyasi va Lombok kabi kod generatorlari. Oddiy, lekin og'ir; maydonga to'g'ridan-to'g'ri murojaatdan getter'ga o'tish API ni buzadi — shuning uchun hamma maydon oldindan getter bilan yoziladi.
JavaScript — get/set sintaksisi va Object.defineProperty(obj, "x", {get, set}). JS atamasida bu "property descriptor" deb ataladi — nomi ham Python'ga o'xshash. Lekin deskriptor obyektning o'ziga yoki prototipiga biriktiriladi; qayta ishlatiladigan "deskriptor klassi" tushunchasi o'rniga odatda dekoratorlar yoki Proxy ishlatiladi.
Kotlin — delegatlangan xossalar: val nom by lazy { ... }, var narx by Delegates.observable(0) { ... }. Delegat getValue/setValue operatorlarini yozadi — Python deskriptoriga eng yaqin analog: qayta ishlatiladigan xossa mantiqini obyektga chiqarish. Farqi — delegat kompilyator tomonidan statik bog'lanadi.
3. Python yondashuvining afzalliklari
| Afzallik | Misol |
|---|---|
| Izchillik | Metod, property va ORM ustuni bir xil qoidalar bilan qidiriladi |
| Kengaytiriluvchanlik | Django, SQLAlchemy, attrs, Pydantic — o'z maydon turlarini yaratadi |
| API ni buzmasdan o'zgartirish | Oddiy atributni keyinchalik property ga aylantirish — chaqiruvchilar sezmaydi (Java'dagi "hamma joyda getter" muammosi yo'q) |
| Qayta ishlatish | Musbat(), Email() — bir marta yozib, yuzlab maydonda |
| Introspeksiya | Klass.maydon — deskriptor obyekti, uning sozlamalari ko'rinadi (sxema, hujjat) |
| O'rgatuvchan | Til ichidagi "sehr" (metod bog'lanishi) o'qiladigan protokol |
4. Kamchiliklari
| Kamchilik | Oqibat |
|---|---|
| Yashirinlik | obj.x — oddiy qiymatmi, hisobmi, tarmoq so'rovimi — ko'rinmaydi |
| Tezlik | Python'da yozilgan deskriptor oddiy atributdan bir necha barobar sekin (bu kompyuterda, Python 3.14 da: oddiy atribut va __slots__ ~40 ns, property ~65 ns, o'z deskriptori ~160 ns atrofida, chaqiruv narxi bilan) |
| Tuzoqlar | Holatni deskriptorda saqlash, bitta nusxa ikki nomda, klassga tayinlash, __slots__ to'qnashuvi |
__getattr__ bilan aralashuv |
Deskriptor ichidagi AttributeError jim yo'qoladi |
| Statik tahlil | Tur tekshiruvchilar deskriptor qaytaradigan turni bilishi uchun annotatsiyalar (__get__ overload'lari) yoki dataclass_transform kerak |
5. Amaliy xulosa
- Protokolni bilish kerak, lekin yozish kamdan-kam: aksariyat hollarda
property,cached_propertyvadataclassyetadi - O'z deskriptoringiz — mantiq ko'p maydon va klassda takrorlanganda, yoki freymvork yozganda
- Freymvork foydalanuvchisi sifatida:
Model.maydonvaobj.maydonfarqini, ORM ustunlarining klassdan murojaatda ifoda qaytarishini tushunish — xatolarni tez topishning kaliti - Tezlik muhim joyda deskriptorlarni issiq sikldan chiqaring yoki
__slots__/oddiy atributlarga o'ting
6. Xulosa
- Python atributga murojaatni yagona deskriptor protokoliga qurgan — til va kutubxonalar bir mexanizmdan foydalanadi
- C# va Java property'larni til sintaksisi yoki konventsiya bilan, JavaScript obyekt darajasidagi accessor'lar bilan, Kotlin esa delegatlar bilan hal qiladi — Kotlin Python'ga eng yaqin
- Afzalliklar: izchillik, kengaytiriluvchanlik, API ni buzmasdan o'zgartirish, qayta ishlatish
- Kamchiliklar: yashirinlik, tezlik, tuzoqlar va statik tahlil murakkabligi
- Protokolni chuqur tushunish — freymvorklar bilan ishlash va ularni yozishning asosi
Nimani mustahkamlaydi: 2.1–2.8-bo'limlar.
Xulosa
Bu darsda — 10-qismning yakunida — atributga murojaatning butun mexanizmini o'rgandik.
Eng muhim uch fikr:
obj.nom— aniq algoritm. Avval turning MRO sidagi data deskriptor, keyin nusxa lug'ati, keyin non-data deskriptor, keyin oddiy klass atributi va oxiridaAttributeErrorbo'lsa__getattr__. Yozishda faqat data deskriptor__set__orqali ushlaydi, aks holda qiymat lug'atga tushadi. Klassdan murojaatda deskriptorlar__get__(None, Klass)oladi,Klass.nom = qiymatesa deskriptorni almashtiradi. Bu qoidalarni 40 qatorlik Python kodida qayta yozish mumkin — va u interpretator bilan bir xil ishlaydi.Deskriptor klassga tegishli — holat nusxada saqlanadi. Qiymatni deskriptorning o'zida saqlash hamma nusxalarni bog'laydi,
WeakKeyDictionaryxeshlanmaydigan nusxalarda buziladi; standart yo'l —__set_name__dan olingan nom bilan nusxa__dict__ida. Har atributga alohida deskriptor nusxasi bering,__slots__bilan nom to'qnashuvidan saqlaning va deskriptor ichidagiAttributeError__getattr__ga tushib, jim yo'qolishi mumkinligini unutmang.Python'ning obyekt modeli shu protokolga qurilgan. Funksiya
__get__bilan bog'langan metodga aylanadi (har murojaatda yangisi),classmethod,staticmethodvaproperty— o'n qatorlik deskriptorlar,__slots__—member_descriptor,cached_property— non-data deskriptor + lug'at. Validatsiya maydonlari, ORM ustunlari va konfiguratsiya tizimlari xuddi shu vositalar bilan quriladi:__set_name__nom beradi,__init_subclass__maydonlarni yig'adi.
10-qism yakuni
| Dars | Mavzu | Asosiy g'oya |
|---|---|---|
| 10.1 | Iterator va iterable | for — iter() + next() + StopIteration; iterator bir martalik |
| 10.2 | __iter__ va __next__ |
Konteyner yangi iterator beradi, iterator o'zini; dangasa ketma-ketliklar |
| 10.3 | Generator va yield |
Pauza qilinadigan funksiya; holatlar, send/throw/close, PEP 479 |
| 10.4 | Generator amaliyoti | Quvurlar, itertools, groupby/tee tuzoqlari, oqimli hisobot |
| 10.5 | yield from |
Qiymat qaytarish, shaffof send/throw, rekursiya narxi |
| 10.6 | Context manager va with |
__enter__/__exit__ protokoli, tanlab yutish, qayta kiriladigan menejerlar |
| 10.7 | contextlib |
@contextmanager ichidan, ExitStack, pop_all, suppress va ExceptionGroup |
| 10.8 | Dekorator — tushuncha | f = A(B(f)), def paytida; qatlamlar tartibi va tuzoqlar |
| 10.9 | Argumentli dekorator | Uch qavat, holat qayerda yashaydi, qavsli/qavssiz naqsh |
| 10.10 | Klass dekoratori | Joyida o'zgartirish, meros bo'lmaydi, slots=True tartibi, __get__ |
| 10.11 | functools.wraps |
Nima ko'chiriladi va nima yo'q; __wrapped__, __signature__ |
| 10.12 | Closure chuqur | Hujayra — quti; kechikkan bog'lanish; closure nimani tirik ushlaydi |
| 10.13 | cache, partial, reduce |
Kalit — chaqiruv shakli; metoddagi kesh sizishi; assotsiativlik |
| 10.14 | Operator yuklash amaliyoti | Joyida amallar, pul turi, algebraik qonunlar, DSL tuzoqlari |
| 10.15 | Ma'lumot modeli | Maxsus metodlar turda qidiriladi; zaxira zanjirlari; = None |
| 10.16 | __new__ va __init__ |
type.__call__; __init__ siz yo'llar; flyweight |
| 10.17 | Deskriptor protokoli | Atributga murojaat algoritmi; holat nusxada; maydonlar freymvorki |
10-qism bilan kursning ikkinchi bosqichi — "Til ustasi" — yakunlandi. Keyingi bosqich — "Python ichidan". 11-qismda obyekt modeli va xotirani o'rganamiz: havolalar, reference counting, axlat yig'uvchi va sikllar, interning, obyektlarning haqiqiy hajmi va weakref — ya'ni shu qismda ko'rgan obyektlar xotirada qanday yashaydi va qachon o'ladi.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!