IlmHamroh
Python kursi/Ilgor til imkoniyatlari17/17-dars33 daqiqa
Mundarija (23)

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:

python
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

python
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):

python
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, faqat AttributeError da. Deskriptor ichidan chiqqan AttributeError ham shu yerga tushadi

2.3. obj.nom = qiymat

object.__setattr__(obj, nom, qiymat):

  1. type(obj).__mro__ da nom data deskriptor bo'lsa → __set__(obj, qiymat) (__set__ yo'q, faqat __delete__ bo'lsa — AttributeError)
  2. 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:

  1. Metaklass (type(Klass)) MRO sida data deskriptor → __get__(Klass, type(Klass))
  2. Klassning o'z MRO si (Klass.__mro__) da topilgan narsa: deskriptor bo'lsa → __get__(None, Klass), aks holda o'zi
  3. 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:

python
narx = Musbat()
class Tovar:
    sotuv = narx
    tannarx = narx          # __set_name__ ikki marta — oxirgi nom ("tannarx") qoladi

Ikkala 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:

python
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 (== esa True)
  • obj.metod.__func__ — funksiya, obj.metod.__self__ — nusxa
  • obj.metod = boshqa — lug'atga yoziladi va metodni yashiradi (non-data)
  • Issiq siklda m = obj.metod ni 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 → funksiya

Shablon

python
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

python
"""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:

text
=== 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_property

Nima 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

python
"""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:

text
=== 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

python
"""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:

text
=== 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 shu

Nima 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.

python
"""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:

text
=== 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

python
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

python
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'ri

3. obj is None ni tekshirmaslik

python
def __get__(self, obj, egasi=None):
    return obj.__dict__[self.nom]        # ❌ Klass.maydon → AttributeError: 'NoneType'

    if obj is None:
        return self                      # ✅ introspeksiya va hujjat uchun

4. Bitta deskriptorni ikki atributga berish

python
musbat = Musbat()
class Tovar:
    narx = musbat
    chegirma = musbat                    # ❌ ikkalasi bitta nom bilan

class Tovar:
    narx = Musbat()
    chegirma = Musbat()                  # ✅

5. Deskriptor ichida AttributeError + __getattr__

python
@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

python
class Nuqta:
    __slots__ = ("x",)
    x = Musbat()                         # ❌ ValueError: conflicts with class variable

class Nuqta:
    __slots__ = ("_x",)
    x = Musbat(slot="_x")                # ✅ deskriptor yashirin slotga yozadi

7. WeakKeyDictionary ga xeshlanmaydigan nusxalar

python
@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__

python
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): partial 3.14 da deskriptor
  • 10.15–10.16-darslar (o'tilgan): ma'lumot modeli, turda qidirish, obyekt yaratilishi
  • 12-qism: CPython ichki tuzilishi — member_descriptor va tur slotlari
  • 13-qism: tur annotatsiyalari, pydantic va dataclass_transform — deskriptorli modellar uchun tekshiruv
  • 21, 23-qismlar: Django models.Field, SQLAlchemy Mapped[...] — deskriptorlar ustiga qurilgan ORM'lar
  • 20-qism: FastAPI'dagi Pydantic modellari va maydon validatsiyasi

8. Eng yaxshi amaliyotlar

  1. Avval oddiy atribut, keyin property, keyin deskriptor. Deskriptor — mantiq ko'p joyda takrorlanganda.

  2. Holatni nusxa __dict__ ida, __set_name__ dan olingan nom bilan saqlang.

  3. __get__ da obj is None → self.

  4. Har atribut uchun alohida deskriptor nusxasi.

  5. Validatsiya uchun data deskriptor, kesh uchun non-data.

  6. Deskriptor ichidagi AttributeError ni ehtiyot qiling — ayniqsa __getattr__ bor klasslarda.

  7. __getattribute__ ni yuklamang, agar juda aniq sabab bo'lmasa.

  8. Issiq siklda bog'langan metodni oldindan oling — qosh = royxat.append.


9. Amaliy topshiriq

Vazifa 1: Natijani bashorat qiling

python
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
  1. yo'q klassdan — _d hali yo'q; klassdan obj is None
  2. 10 {'_d': 10} — __set__ ikkiga ko'paytirdi
  3. nondata va keyingi qatorda o'zim — non-data lug'at bilan yashirildi
  4. yo'q — data deskriptor lug'atdagi d dan ustun, _d esa yo'q
  5. False True
  6. almashtirildi — funksiya non-data deskriptor
  7. function method
  8. AttributeError — setter yo'q
  9. zaxira — property ichidagi AttributeError __getattr__ ga o'tdi
  10. member_descriptor
  11. 2 {'_b': 2} — __set_name__ ikki marta, oxirgi nom b; t.a = 1 ham _b ga yozdi
  12. 1 1 — klassga tayinlash deskriptorni almashtirdi

Vazifa 2: Xatolarni tuzating

python
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 = maydon
Javoblar
python
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):

  1. Metaklass MRO sidagi data deskriptorlar
  2. Klassning o'z MRO sidagi deskriptorlar uchun __get__(None, Klass)
  3. Metaklassdagi non-data deskriptorlar va oddiy atributlar
  4. type va o'zingizning metaklassingiz (metaklassda property bilan) ustida getattr bilan solishtiring

Vazifa 4: Birlikli maydonlar

Birlikli(asosiy="m", ruxsat=("m", "km", "sm")) deskriptori yozing:

  1. obj.masofa = (5, "km") — asosiy birlikka o'girib saqlasin
  2. obj.masofa — asosiy birlikdagi son; Klass.masofa.qanday(obj, "sm") — boshqa birlikda
  3. Noma'lum birlik — ValueError
  4. __slots__ li va __dict__ li klasslarda ishlasin (saqlash joyini avtomatik tanlasin)
  5. Misol 4 dagi Model bilan birlashtiring

Vazifa 5: Kuzatiladigan atributlar

Kuzatiladigan() deskriptori yozing:

  1. Qiymat o'zgarganda obyektga ulangan kuzatuvchilar chaqirilsin: obj.kuzat("narx", f) → f(eski, yangi)
  2. Kuzatuvchilar obyektni tirik ushlamasin (weakref.WeakMethod — 10.12-dars)
  3. with obj.jim(): bloki ichida xabarnomalar to'plansin va blok oxirida bir marta yuborilsin
  4. Qiymat o'zgarmasa (eski == yangi) — xabar yuborilmasin

Vazifa 6: ORM ustunlari

Kichik ORM yozing:

  1. Ustun(tur, asosiy_kalit=False) deskriptori va Jadval bazaviy klassi (__init_subclass__)
  2. Klass.ustun — klassdan murojaatda 10.14-darsdagi Ifoda obyektini qaytarsin: Foydalanuvchi.yosh >= 18 → SQL ifoda
  3. obj.ustun — nusxadan murojaatda qiymat, o'zgartirilgan ustunlar obj._ozgargan to'plamiga tushsin
  4. Jadval.yarat_sql() — CREATE TABLE, obj.saqla_sql() — faqat o'zgargan ustunlar bilan UPDATE
  5. sqlite3 bilan 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 darajasidagi member_descriptor
  • super() — 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

  1. Protokolni bilish kerak, lekin yozish kamdan-kam: aksariyat hollarda property, cached_property va dataclass yetadi
  2. O'z deskriptoringiz — mantiq ko'p maydon va klassda takrorlanganda, yoki freymvork yozganda
  3. Freymvork foydalanuvchisi sifatida: Model.maydon va obj.maydon farqini, ORM ustunlarining klassdan murojaatda ifoda qaytarishini tushunish — xatolarni tez topishning kaliti
  4. Tezlik muhim joyda deskriptorlarni issiq sikldan chiqaring yoki __slots__/oddiy atributlarga o'ting

6. Xulosa

  1. Python atributga murojaatni yagona deskriptor protokoliga qurgan — til va kutubxonalar bir mexanizmdan foydalanadi
  2. 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
  3. Afzalliklar: izchillik, kengaytiriluvchanlik, API ni buzmasdan o'zgartirish, qayta ishlatish
  4. Kamchiliklar: yashirinlik, tezlik, tuzoqlar va statik tahlil murakkabligi
  5. 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:

  1. obj.nom — aniq algoritm. Avval turning MRO sidagi data deskriptor, keyin nusxa lug'ati, keyin non-data deskriptor, keyin oddiy klass atributi va oxirida AttributeError bo'lsa __getattr__. Yozishda faqat data deskriptor __set__ orqali ushlaydi, aks holda qiymat lug'atga tushadi. Klassdan murojaatda deskriptorlar __get__(None, Klass) oladi, Klass.nom = qiymat esa deskriptorni almashtiradi. Bu qoidalarni 40 qatorlik Python kodida qayta yozish mumkin — va u interpretator bilan bir xil ishlaydi.

  2. Deskriptor klassga tegishli — holat nusxada saqlanadi. Qiymatni deskriptorning o'zida saqlash hamma nusxalarni bog'laydi, WeakKeyDictionary xeshlanmaydigan 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 ichidagi AttributeError __getattr__ ga tushib, jim yo'qolishi mumkinligini unutmang.

  3. Python'ning obyekt modeli shu protokolga qurilgan. Funksiya __get__ bilan bog'langan metodga aylanadi (har murojaatda yangisi), classmethod, staticmethod va property — 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.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
10.17-dars: Descriptor protokoli — IlmHamroh