IlmHamroh
Python kursi/Ilgor til imkoniyatlari9/17-dars29 daqiqa
Mundarija (23)

10.9-dars: Argumentli dekorator

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


1. Kirish va motivatsiya

Oldingi darsdagi dekoratorlar hamma funksiyaga bir xil ishlardi. Real loyihada esa sozlash kerak:

python
@qayta_urin(urinishlar=5, istisnolar=(ConnectionError, TimeoutError))
def narx_ol(mahsulot): ...

@tezlik_chegarasi(100, davr=60)
def xabar_yubor(matn): ...

@warnings.deprecated("yangi_hisobot() ishlating")
def eski_hisobot(): ...

7.11-darsda bunday dekorator "uch qavatli funksiya" ekanini ko'rgan edik. Endi mana bu kodga qarang:

python
SOZLAMA = {"urinishlar": 2}

@qayta_urin(SOZLAMA["urinishlar"])
def yukla(): ...

def main():
    SOZLAMA["urinishlar"] = 10       # konfiguratsiya fayldan o'qildi
    yukla()                          # necha marta urinadi?

Javob — 2 marta. SOZLAMA["urinishlar"] import paytida, def bilan birga bir marta hisoblangan. Keyingi o'zgarish dekoratorga yetib bormaydi.

Argumentli dekoratorda uchta alohida lahza va uchta alohida hayot davri bor. Qaysi kod qachon ishlashini va qaysi o'zgaruvchi kimga tegishli ekanini bilmasangiz, bunday xatolar muqarrar.

Bu darsda:

  • Uch qavat: fabrika, dekorator, o'rovchi — har biri qachon ishlaydi
  • @f(x) nimaga ochiladi
  • Holat qaysi qavatda yashaydi va kim bilan bo'lishiladi
  • Argumentlar import paytida hisoblanishi va o'zgaruvchan standart qiymat tuzog'i
  • Qavsli va qavssiz ishlatiladigan dekorator
  • Sinf asosidagi argumentli dekorator
  • Dekoratsiya paytida tekshirish (fail fast)

2. Nazariya — chuqur tushuntirish

2.1. @f(x) nimaga ochiladi

PEP 614 bo'yicha @ dan keyin istalgan ifoda turadi (10.8-dars). qayta_urin(3) — shunchaki chaqiruv ifodasi, uning natijasi dekorator bo'lishi kerak:

python
@qayta_urin(3)
def yukla(): ...
python
_dekorator = qayta_urin(3)      # 1. fabrika chaqiriladi → dekorator qaytadi
yukla = _dekorator(yukla)       # 2. dekorator funksiyaga qo'llanadi → o'rovchi

Shuning uchun argumentli dekorator — dekorator qaytaradigan funksiya (dekorator fabrikasi):

python
def qayta_urin(urinishlar):                      # 1-qavat: fabrika
    def dekorator(funksiya):                     # 2-qavat: dekorator
        def orovchi(*args, **kwargs):            # 3-qavat: o'rovchi
            for i in range(urinishlar):
                try:
                    return funksiya(*args, **kwargs)
                except ConnectionError:
                    if i == urinishlar - 1:
                        raise
        return orovchi
    return dekorator

2.2. Uch qavat — uch lahza

Qavat Qachon ishlaydi Necha marta Nima biladi
Fabrika @...(...) ifodasi baholanganda Har @fabrika(...) yozuvi uchun bir marta Argumentlar
Dekorator Funksiya aniqlanganda Har bezatilgan funksiya uchun bir marta Argumentlar + funksiya
O'rovchi Funksiya chaqirilganda Har chaqiruvda Argumentlar + funksiya + chaqiruv argumentlari

Birinchi ikki qavat modul funksiyalari uchun import paytida ishlaydi. Faqat uchinchisi — ish vaqtida.

2.3. Holat qaysi qavatda yashaydi

Har qavatdagi lokal o'zgaruvchi closure orqali ichki qavatlarga ko'rinadi (10.12-dars), lekin hayot davri qavatga bog'liq:

python
def sanagich(nom):
    jami = 0                        # fabrika chaqiruviga tegishli
    def dekorator(funksiya):
        soni = 0                    # har bir funksiyaga alohida
        def orovchi(*args):
            nonlocal jami, soni
            jami += 1
            soni += 1
            boshlandi = time.perf_counter()     # faqat shu chaqiruvga
            ...
        return orovchi
    return dekorator
python
api = sanagich("api")               # bitta fabrika chaqiruvi

@api
def royxat(): ...                   # jami — umumiy

@api
def profil(): ...                   # jami — umumiy, soni — alohida

@sanagich("db")
def saqla(): ...                    # yangi fabrika chaqiruvi — yangi jami

Qoida: bir nechta funksiya bo'lishishi kerak bo'lgan narsa (umumiy tezlik chegarasi, umumiy hisoblagich) — fabrika qavatida. Har funksiyaga alohida (kesh, chaqiruvlar soni) — dekorator qavatida. Har chaqiruvga (vaqt, urinish raqami) — o'rovchida.

2.4. Argumentlar bir marta hisoblanadi

python
@qayta_urin(SOZLAMA["urinishlar"])      # import paytida: 2
def yukla(): ...

SOZLAMA["urinishlar"] = 10              # dekorator bilmaydi

Yechimlar:

  1. Chaqiriladigan obyekt uzating va o'rovchida chaqiring: @qayta_urin(lambda: SOZLAMA["urinishlar"])
  2. Sozlama obyektini uzating va o'rovchida o'qing: @qayta_urin(sozlama=SOZLAMA)
  3. None — "chaqiruv paytida sozlamadan o'qi" degani bo'lsin

2.5. O'zgaruvchan standart qiymat — yangi ko'rinishda

7.3-darsdagi tuzoq fabrikada ham ishlaydi:

python
def kesh(saqlash={}):                   # ❌ bitta lug'at hamma chaqiruvlar uchun
    def dekorator(funksiya):
        def orovchi(n):
            if n not in saqlash:
                saqlash[n] = funksiya(n)
            return saqlash[n]
        return orovchi
    return dekorator

@kesh()
def kvadrat(n): return n * n

@kesh()
def kub(n): return n ** 3

kvadrat(2), kub(2)                      # (4, 4) — kub kvadratning keshini oldi!

saqlash=None va dekorator qavatida saqlash = {} if saqlash is None else saqlash.

2.6. Qavsli va qavssiz — bir dekorator

Ba'zan ikkala shakl ham qulay: @jurnal va @jurnal(daraja="XATO"). Standart naqsh:

python
def jurnal(funksiya=None, /, *, daraja="INFO"):
    def dekorator(f):
        def orovchi(*args, **kwargs):
            print(f"[{daraja}] {f.__name__}")
            return f(*args, **kwargs)
        return orovchi

    if funksiya is None:
        return dekorator                # @jurnal(...) yoki @jurnal()
    return dekorator(funksiya)          # @jurnal

Muhim tafsilotlar:

  • Sozlamalar faqat nomli (*) — aks holda @jurnal("XATO") da "XATO" funksiya o'rniga tushadi
  • Birinchi parametr faqat pozitsion (/) — funksiya= nomi bilan berib bo'lmaydi
  • Birinchi pozitsion argumentni tekshiring: callable bo'lmasa — tushunarli TypeError

Bu naqsh dekoratorning birinchi sozlamasi o'zi chaqiriladigan bo'lsa buziladi. @saralangan(len) da len bezatiladigan funksiya deb tushuniladi. Bunday sozlamalar faqat nomli bo'lishi shart: @saralangan(kalit=len).

2.7. Sinf asosidagi argumentli dekorator

python
class TezlikChegarasi:
    def __init__(self, soni, davr):     # 1-qavat — fabrika
        self.soni, self.davr = soni, davr
        self.vaqtlar = deque()

    def __call__(self, funksiya):       # 2-qavat — dekorator
        def orovchi(*args, **kwargs):   # 3-qavat — o'rovchi
            ...
        return orovchi

@TezlikChegarasi(100, davr=60) — avval nusxa yaratiladi, keyin u funksiyaga qo'llanadi.

Afzalliklari:

  • Fabrika holati — oddiy atributlar, nonlocal kerak emas
  • Holatni tashqaridan ko'rish va boshqarish mumkin: chegara.tozala(), chegara.qolgan
  • Bitta nusxani bir nechta funksiyaga qo'llash — umumiy chegara aniq ko'rinadi

2.8. Dekoratsiya paytida tekshirish

Dekorator qavati funksiyani ko'radi — noto'g'ri sozlamani import paytida aniqlash mumkin:

python
def tekshir(**turlar):
    def dekorator(funksiya):
        imzo = inspect.signature(funksiya)
        yoq = set(turlar) - set(imzo.parameters)
        if yoq:
            raise ValueError(f"{funksiya.__name__} da parametr yo'q: {yoq}")   # ⭐ importda
        def orovchi(*args, **kwargs):
            bog = imzo.bind(*args, **kwargs)                                  # chaqiruvda
            ...
        return orovchi
    return dekorator

Xato test yoki ishlab chiqarishda emas, modul yuklanganda chiqadi.

2.9. Standart kutubxonadagi argumentli dekoratorlar

Dekorator Nima qiladi
functools.lru_cache(maxsize=128) Cheklangan kesh (10.13-dars)
functools.wraps(asl) Metama'lumotni ko'chiradi (10.11-dars)
functools.singledispatch + .register(tur) Tur bo'yicha tanlash
dataclasses.dataclass(frozen=True, slots=True) Klass yaratish (8.17-dars)
warnings.deprecated("xabar") (3.13+) Chaqiruvda DeprecationWarning
unittest.mock.patch("modul.nom") Testda almashtirish

lru_cache va dataclass ikkala shaklda ham ishlaydi — 2.6-bo'limdagi naqsh.


3. Tez ma'lumotnoma

Uch qavat

python
def fabrika(sozlama):               # @fabrika(...) — importda
    def dekorator(funksiya):        # def paytida
        def orovchi(*args, **kwargs):   # har chaqiruvda
            return funksiya(*args, **kwargs)
        return orovchi
    return dekorator

Ochilishi

@fabrika(x)  def f   →   f = fabrika(x)(f)

Holat

fabrika qavati    → shu fabrika chaqiruvidan bezatilgan barcha funksiyalar uchun umumiy
dekorator qavati  → har funksiyaga alohida
o'rovchi          → har chaqiruvga alohida

Qavsli/qavssiz

python
def dek(funksiya=None, /, *, sozlama=...):
    def dekorator(f): ...
    if funksiya is None:
        return dekorator
    if not callable(funksiya):
        raise TypeError("sozlamalarni nomi bilan bering")
    return dekorator(funksiya)

Xatolar

@fabrika   (qavs unutildi)    → f = dekorator; f() → TypeError: missing ... 'funksiya'
@dekorator() (ortiqcha qavs)  → def paytida TypeError: missing ... 'funksiya'
@fabrika(SOZLAMA[x])          → import paytidagi qiymat
def fabrika(kesh={})          → hamma bezatilgan funksiyalar bitta lug'at

4. Batafsil misollar

Misol 1 — Uch qavatni kuzatish

python
"""Fabrika, dekorator va o'rovchi qachon ishlaydi; qavsni unutish yoki ortiqcha qo'yish."""


def korinish(x):
    return repr(x) if isinstance(x, str) else f"<{type(x).__name__} {x.__name__}>"


def kuzat(yorliq):
    print(f"  [1-qavat] fabrika: yorliq={korinish(yorliq)}")

    def dekorator(funksiya):
        print(f"  [2-qavat] dekorator: {funksiya.__name__} bezatildi")

        def orovchi(*args, **kwargs):
            print(f"  [3-qavat] o'rovchi: {yorliq} → {funksiya.__name__}{args}")
            return funksiya(*args, **kwargs)

        return orovchi

    return dekorator


print("=== 1. Bitta dekorator, ikki chaqiruv ===")


@kuzat("API")
def narx(mahsulot):
    return {"olma": 12_000}.get(mahsulot, 0)


print("  --- def tugadi, endi chaqiramiz ---")
print(f"  natija: {narx('olma')}")
print(f"  natija: {narx('nok')}")

print("\n=== 2. Qo'lda ochilgan shakli ===")


def ombor(mahsulot):
    return 5


dekorator = kuzat("DB")
ombor = dekorator(ombor)
ombor("olma")

print("\n=== 3. Bitta fabrika natijasi — ikki funksiya ===")
api = kuzat("API-v2")


@api
def royxat():
    return []


@api
def profil():
    return {}


royxat()
profil()

print("\n=== 4. Argument ifodasi def dan oldin baholanadi ===")


def yorliq_ol():
    print("  yorliq_ol() chaqirildi")
    return "HISOBOT"


print("  def dan oldin")


@kuzat(yorliq_ol())
def hisobot():
    return "tayyor"


print("  def dan keyin")

print("\n=== 5. ⚠️ Qavsni unutish ===")


@kuzat
def unutilgan():
    return 1


print(f"  unutilgan endi: {unutilgan.__name__}")
try:
    unutilgan()
except TypeError as e:
    print(f"  ❌ chaqiruvda TypeError: {e}")

print("\n=== 6. ⚠️ Argumentsiz dekoratorga ortiqcha qavs ===")


def oddiy(funksiya):
    def orovchi(*args, **kwargs):
        return funksiya(*args, **kwargs)
    return orovchi


try:
    @oddiy()
    def ortiqcha():
        return 1
except TypeError as e:
    print(f"  ❌ def paytida TypeError: {e}")

Natijaning muhim qismi:

text
=== 1. Bitta dekorator, ikki chaqiruv ===
  [1-qavat] fabrika: yorliq='API'
  [2-qavat] dekorator: narx bezatildi
  --- def tugadi, endi chaqiramiz ---
  [3-qavat] o'rovchi: API → narx('olma',)
  natija: 12000
  [3-qavat] o'rovchi: API → narx('nok',)
  natija: 0

=== 2. Qo'lda ochilgan shakli ===
  [1-qavat] fabrika: yorliq='DB'
  [2-qavat] dekorator: ombor bezatildi
  [3-qavat] o'rovchi: DB → ombor('olma',)

=== 3. Bitta fabrika natijasi — ikki funksiya ===
  [1-qavat] fabrika: yorliq='API-v2'
  [2-qavat] dekorator: royxat bezatildi
  [2-qavat] dekorator: profil bezatildi
  [3-qavat] o'rovchi: API-v2 → royxat()
  [3-qavat] o'rovchi: API-v2 → profil()

=== 4. Argument ifodasi def dan oldin baholanadi ===
  def dan oldin
  yorliq_ol() chaqirildi
  [1-qavat] fabrika: yorliq='HISOBOT'
  [2-qavat] dekorator: hisobot bezatildi
  def dan keyin

=== 5. ⚠️ Qavsni unutish ===
  [1-qavat] fabrika: yorliq=<function unutilgan>
  unutilgan endi: dekorator
  ❌ chaqiruvda TypeError: kuzat.<locals>.dekorator() missing 1 required positional argument: 'funksiya'

=== 6. ⚠️ Argumentsiz dekoratorga ortiqcha qavs ===
  ❌ def paytida TypeError: oddiy() missing 1 required positional argument: 'funksiya'

Nima ko'rsatdi: 2.1, 2.2-bo'limlar. 5-bo'limda unutilgan funksiya "jim" dekoratorga aylandi va xato faqat chaqiruvda chiqdi, 6-bo'limda esa xato darhol def paytida chiqdi.

Misol 2 — Holat qavatlari va import paytidagi tuzoqlar

python
"""Qaysi holat kim bilan bo'lishiladi; bir marta hisoblanadigan argumentlar; o'zgaruvchan standart qiymat."""


def sanagich(nom):
    jami = 0

    def dekorator(funksiya):
        soni = 0

        def orovchi(*args, **kwargs):
            nonlocal jami, soni
            jami += 1
            soni += 1
            return funksiya(*args, **kwargs)

        orovchi.holat = lambda: f"{nom}.{funksiya.__name__}: soni={soni}, {nom} jami={jami}"
        return orovchi

    return dekorator


print("=== 1. Fabrika qavati — umumiy, dekorator qavati — alohida ===")
api = sanagich("api")


@api
def royxat():
    pass


@api
def profil():
    pass


@sanagich("db")
def saqla():
    pass


for f in (royxat, royxat, royxat, profil, saqla, saqla):
    f()
for f in (royxat, profil, saqla):
    print(f"  {f.holat()}")

print("\n=== 2. ⚠️ Argument import paytida hisoblanadi ===")
SOZLAMA = {"urinishlar": 2}


def qayta_urin(urinishlar):
    def dekorator(funksiya):
        def orovchi(*args, **kwargs):
            chegara = urinishlar() if callable(urinishlar) else urinishlar
            for i in range(1, chegara + 1):
                try:
                    return funksiya(*args, **kwargs)
                except ConnectionError:
                    if i == chegara:
                        raise
        return orovchi
    return dekorator


def beqaror(nechta_xato):
    holat = {"chaqiruv": 0}

    def sorov():
        holat["chaqiruv"] += 1
        if holat["chaqiruv"] <= nechta_xato:
            raise ConnectionError(f"{holat['chaqiruv']}-urinish uzildi")
        return f"ok ({holat['chaqiruv']}-urinishda)"

    return sorov


yukla = qayta_urin(SOZLAMA["urinishlar"])(beqaror(3))
yukla_dangasa = qayta_urin(lambda: SOZLAMA["urinishlar"])(beqaror(3))

SOZLAMA["urinishlar"] = 5
print(f"  SOZLAMA endi: {SOZLAMA}")
for nom, f in [("qiymat uzatilgan", yukla), ("lambda uzatilgan", yukla_dangasa)]:
    try:
        print(f"  {nom}: {f()}")
    except ConnectionError as e:
        print(f"  {nom}: ❌ {e} — hali ham 2 marta urindi")

print("\n=== 3. ⚠️ O'zgaruvchan standart qiymat fabrikada ===")


def kesh_yomon(saqlash={}):
    def dekorator(funksiya):
        def orovchi(n):
            if n not in saqlash:
                saqlash[n] = funksiya(n)
            return saqlash[n]
        return orovchi
    return dekorator


def kesh(saqlash=None):
    def dekorator(funksiya):
        joy = {} if saqlash is None else saqlash
        def orovchi(n):
            if n not in joy:
                joy[n] = funksiya(n)
            return joy[n]
        orovchi.joy = joy
        return orovchi
    return dekorator


@kesh_yomon()
def kvadrat_y(n):
    return n * n


@kesh_yomon()
def kub_y(n):
    return n ** 3


@kesh()
def kvadrat(n):
    return n * n


@kesh()
def kub(n):
    return n ** 3


print(f"  ❌ kesh_yomon: kvadrat(2)={kvadrat_y(2)}, kub(2)={kub_y(2)}")
print(f"  ✅ kesh:       kvadrat(2)={kvadrat(2)}, kub(2)={kub(2)}")
umumiy = {}


@kesh(saqlash=umumiy)
def a(n):
    return f"a{n}"


@kesh(saqlash=umumiy)
def b(n):
    return f"b{n}"


a(1)
b(2)
print(f"  ataylab umumiy kesh: {umumiy}")

print("\n=== 4. Siklda dekorator yaratish: qiymat chaqiruv paytida ushlanadi ===")


def daraja(nomi):
    def dekorator(funksiya):
        def orovchi(xabar):
            return f"[{nomi}] {funksiya(xabar)}"
        return orovchi
    return dekorator


ishlovchilar = {}
for d in ["INFO", "OGOHLANTIRISH", "XATO"]:
    @daraja(d)
    def ishlovchi(xabar):
        return xabar.lower()
    ishlovchilar[d] = ishlovchi

lambdalar = {d: (lambda xabar: f"[{d}] {xabar.lower()}") for d in ["INFO", "XATO"]}
print(f"  ✅ fabrika bilan:          {ishlovchilar['INFO']('SALOM')}, {ishlovchilar['XATO']('SALOM')}")
print(f"  ⚠️ comprehension + lambda: {lambdalar['INFO']('SALOM')}, {lambdalar['XATO']('SALOM')}")
print("     lambda d ning qiymatini emas, o'zgaruvchini eslaydi — kechikkan bog'lanish 10.12-bob")
print("     fabrika chaqiruvi esa d ning o'sha paytdagi qiymatini o'z parametriga oldi")

Natijaning muhim qismi:

text
=== 1. Fabrika qavati — umumiy, dekorator qavati — alohida ===
  api.royxat: soni=3, api jami=4
  api.profil: soni=1, api jami=4
  db.saqla: soni=2, db jami=2

=== 2. ⚠️ Argument import paytida hisoblanadi ===
  SOZLAMA endi: {'urinishlar': 5}
  qiymat uzatilgan: ❌ 2-urinish uzildi — hali ham 2 marta urindi
  lambda uzatilgan: ok (4-urinishda)

=== 3. ⚠️ O'zgaruvchan standart qiymat fabrikada ===
  ❌ kesh_yomon: kvadrat(2)=4, kub(2)=4
  ✅ kesh:       kvadrat(2)=4, kub(2)=8
  ataylab umumiy kesh: {1: 'a1', 2: 'b2'}

=== 4. Siklda dekorator yaratish: qiymat chaqiruv paytida ushlanadi ===
  ✅ fabrika bilan:          [INFO] salom, [XATO] salom
  ⚠️ comprehension + lambda: [XATO] salom, [XATO] salom
     lambda d ning qiymatini emas, o'zgaruvchini eslaydi — kechikkan bog'lanish 10.12-bob
     fabrika chaqiruvi esa d ning o'sha paytdagi qiymatini o'z parametriga oldi

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

Misol 3 — Qavsli va qavssiz, sinf asosida

python
"""Bir dekorator — to'rt xil chaqiruv; noaniqlik tuzog'i; sinf asosidagi dekorator fabrikasi."""


def jurnal(funksiya=None, /, *, daraja="INFO", prefiks=""):
    if funksiya is not None and not callable(funksiya):
        raise TypeError(
            f"jurnal: sozlamalarni nomi bilan bering, masalan @jurnal(daraja={funksiya!r})"
        )

    def dekorator(f):
        def orovchi(*args, **kwargs):
            natija = f(*args, **kwargs)
            print(f"    [{daraja}] {prefiks}{f.__name__}{args} → {natija!r}")
            return natija
        return orovchi

    if funksiya is None:
        return dekorator
    return dekorator(funksiya)


print("=== 1. To'rt shakl ===")


@jurnal
def qosh(a, b):
    return a + b


@jurnal()
def ayir(a, b):
    return a - b


@jurnal(daraja="XATO", prefiks="hisob.")
def bol(a, b):
    return a / b


def kopaytir(a, b):
    return a * b


kopaytir = jurnal(kopaytir, daraja="DEBUG")

qosh(2, 3)
ayir(2, 3)
bol(2, 4)
kopaytir(2, 3)

print("\n=== 2. ⚠️ Sozlamani pozitsion berish ===")
try:
    @jurnal("XATO")
    def xato_shakl():
        pass
except TypeError as e:
    print(f"  ✅ darhol tushunarli xato: {e}")


def jurnal_tekshiruvsiz(funksiya=None, *, daraja="INFO"):
    def dekorator(f):
        def orovchi(*args):
            return f(*args)
        return orovchi
    return dekorator if funksiya is None else dekorator(funksiya)


try:
    @jurnal_tekshiruvsiz("XATO")
    def xato_shakl2():
        pass
except TypeError as e:
    print(f"  ❌ tekshiruvsiz variant: TypeError: {e}")

print("\n=== 3. ⚠️ Noaniqlik: birinchi sozlama o'zi chaqiriladigan ===")


def saralangan(birinchi=None, /, *, teskari=False):
    """❌ Birinchi pozitsion argument — yo bezatiladigan funksiya, yo kalit funksiyasi."""
    def dekorator(f, kalit=None):
        def orovchi(*args):
            return sorted(f(*args), key=kalit, reverse=teskari)
        return orovchi
    if callable(birinchi):
        return dekorator(birinchi)                   # @saralangan deb taxmin qilamiz
    return lambda f: dekorator(f, kalit=birinchi)


@saralangan
def mevalar():
    return ["o'rik", "nok", "olma", "anor"]


print(f"  @saralangan:        {mevalar()}")
try:
    @saralangan(len)                                 # len ham callable — funksiya deb o'ylandi
    def mevalar_uzunlik():
        return ["o'rik", "nok", "olma", "anor"]
    print(f"  @saralangan(len):   {mevalar_uzunlik()}")
except TypeError as e:
    print(f"  ❌ @saralangan(len): def paytida TypeError: {e}")
    print("     saralangan(len) — len ni o'radi; keyin o'sha o'rovchi mevalar_uzunlik ni oldi")


def saralangan2(funksiya=None, /, *, kalit=None, teskari=False):
    """✅ Chaqiriladigan sozlama faqat nomli."""
    def dekorator(f):
        def orovchi(*args):
            return sorted(f(*args), key=kalit, reverse=teskari)
        return orovchi
    return dekorator if funksiya is None else dekorator(funksiya)


@saralangan2(kalit=len, teskari=True)
def mevalar_uzunlik2():
    return ["o'rik", "nok", "olma", "anor"]


print(f"  ✅ @saralangan2(kalit=len, teskari=True): {mevalar_uzunlik2()}")

print("\n=== 4. Sinf asosida: holat ochiq atributlarda ===")


class Hisoblagich:
    def __init__(self, nom, *, chegara=None):
        self.nom = nom
        self.chegara = chegara
        self.soni = {}

    def __call__(self, funksiya):
        self.soni[funksiya.__name__] = 0

        def orovchi(*args, **kwargs):
            if self.chegara is not None and self.jami >= self.chegara:
                raise RuntimeError(f"{self.nom}: chegara {self.chegara} ga yetildi")
            self.soni[funksiya.__name__] += 1
            return funksiya(*args, **kwargs)

        return orovchi

    @property
    def jami(self):
        return sum(self.soni.values())


tolovlar = Hisoblagich("to'lovlar", chegara=4)


@tolovlar
def karta(summa):
    return f"karta {summa}"


@tolovlar
def naqd(summa):
    return f"naqd {summa}"


for f, s in [(karta, 100), (naqd, 50), (karta, 70), (karta, 10), (naqd, 5)]:
    try:
        f(s)
    except RuntimeError as e:
        print(f"  ❌ {e}")
print(f"  soni: {tolovlar.soni}, jami: {tolovlar.jami}")
tolovlar.chegara = None
print(f"  chegara olib tashlandi → {naqd(5)}, jami: {tolovlar.jami}")

Natijaning muhim qismi:

text
=== 1. To'rt shakl ===
    [INFO] qosh(2, 3) → 5
    [INFO] ayir(2, 3) → -1
    [XATO] hisob.bol(2, 4) → 0.5
    [DEBUG] kopaytir(2, 3) → 6

=== 2. ⚠️ Sozlamani pozitsion berish ===
  ✅ darhol tushunarli xato: jurnal: sozlamalarni nomi bilan bering, masalan @jurnal(daraja='XATO')
  ❌ tekshiruvsiz variant: TypeError: 'str' object is not callable

=== 3. ⚠️ Noaniqlik: birinchi sozlama o'zi chaqiriladigan ===
  @saralangan:        ['anor', 'nok', "o'rik", 'olma']
  ❌ @saralangan(len): def paytida TypeError: object of type 'function' has no len()
     saralangan(len) — len ni o'radi; keyin o'sha o'rovchi mevalar_uzunlik ni oldi
  ✅ @saralangan2(kalit=len, teskari=True): ["o'rik", 'olma', 'anor', 'nok']

=== 4. Sinf asosida: holat ochiq atributlarda ===
  ❌ to'lovlar: chegara 4 ga yetildi
  soni: {'karta': 3, 'naqd': 1}, jami: 4
  chegara olib tashlandi → naqd 5, jami: 5

Nima ko'rsatdi: 2.6, 2.7-bo'limlar. 3-bo'lim — qavsli/qavssiz naqshni birinchi sozlamasi funksiya bo'lgan dekoratorga qo'llamaslik kerakligining isboti.

Misol 4 — Amaliy: API mijoz uchun dekoratorlar

Tashqi API bilan ishlovchi kod: qayta urinish (eksponensial kutish bilan), tezlik chegarasi va argument turlarini tekshirish. Vaqt deterministik bo'lishi uchun soxta soat ishlatamiz.

python
"""qayta_urin, TezlikChegarasi, tekshir — soxta soat bilan; dekoratsiya paytidagi tekshiruv; tartib."""

import inspect
from collections import deque


class SoxtaSoat:
    def __init__(self):
        self.t = 0.0
        self.uxlashlar = []

    def __call__(self):
        return self.t

    def uxla(self, soniya):
        self.uxlashlar.append(soniya)
        self.t += soniya


soat = SoxtaSoat()


def qayta_urin(urinishlar=3, /, *, istisnolar=(ConnectionError,), kutish=0.5, kopaytiruvchi=2, uxla=None):
    if urinishlar < 1:
        raise ValueError(f"urinishlar >= 1 bo'lishi kerak, berildi: {urinishlar}")
    uxla = uxla or soat.uxla

    def dekorator(funksiya):
        def orovchi(*args, **kwargs):
            pauza = kutish
            for i in range(1, urinishlar + 1):
                try:
                    return funksiya(*args, **kwargs)
                except istisnolar as e:
                    if i == urinishlar:
                        raise
                    print(f"      {funksiya.__name__}: {type(e).__name__} → {pauza}s kutib, {i + 1}-urinish")
                    uxla(pauza)
                    pauza *= kopaytiruvchi
        orovchi.__name__ = funksiya.__name__
        return orovchi
    return dekorator


class TezlikXatosi(Exception):
    pass


class TezlikChegarasi:
    """davr soniya ichida ko'pi bilan soni ta chaqiruv (sirpanuvchi oyna)."""

    def __init__(self, soni, *, davr, soat=soat):
        self.soni, self.davr, self.soat = soni, davr, soat
        self.vaqtlar = deque()

    def __call__(self, funksiya):
        def orovchi(*args, **kwargs):
            hozir = self.soat()
            while self.vaqtlar and hozir - self.vaqtlar[0] >= self.davr:
                self.vaqtlar.popleft()
            if len(self.vaqtlar) >= self.soni:
                kut = self.davr - (hozir - self.vaqtlar[0])
                raise TezlikXatosi(f"{funksiya.__name__}: chegara {self.soni}/{self.davr}s, {kut:.1f}s kuting")
            self.vaqtlar.append(hozir)
            return funksiya(*args, **kwargs)
        orovchi.__name__ = funksiya.__name__
        return orovchi


def tekshir(**turlar):
    def dekorator(funksiya):
        imzo = inspect.signature(funksiya)
        yoq = sorted(set(turlar) - set(imzo.parameters))
        if yoq:
            raise ValueError(f"@tekshir: {funksiya.__name__} da bunday parametr yo'q: {yoq}")

        def orovchi(*args, **kwargs):
            bog = imzo.bind(*args, **kwargs)
            bog.apply_defaults()
            for nom, tur in turlar.items():
                qiymat = bog.arguments[nom]
                if not isinstance(qiymat, tur):
                    raise TypeError(f"{funksiya.__name__}: {nom} {tur.__name__} bo'lishi kerak, "
                                    f"{type(qiymat).__name__} berildi ({qiymat!r})")
            return funksiya(*args, **kwargs)
        orovchi.__name__ = funksiya.__name__
        return orovchi
    return dekorator


class SoxtaServer:
    def __init__(self, stsenariy):
        self.stsenariy = deque(stsenariy)
        self.sorovlar = 0

    def javob(self, nima):
        self.sorovlar += 1
        natija = self.stsenariy.popleft() if self.stsenariy else "ok"
        if isinstance(natija, Exception):
            raise natija
        return f"{nima}: {natija}"


print("=== 1. Ikki uzilishdan keyin muvaffaqiyat ===")
server = SoxtaServer([ConnectionError("uzildi"), TimeoutError("javob yo'q"), "12 000 so'm"])


@qayta_urin(4, istisnolar=(ConnectionError, TimeoutError))
def narx_ol(mahsulot):
    return server.javob(mahsulot)


print(f"  {narx_ol('olma')}")
print(f"  so'rovlar: {server.sorovlar}, uxlashlar: {soat.uxlashlar}, soat: {soat()}s")

print("\n=== 2. Hamma urinishlar muvaffaqiyatsiz ===")
soat.uxlashlar.clear()
server = SoxtaServer([ConnectionError("1"), ConnectionError("2"), ConnectionError("3"), ConnectionError("4")])
try:
    narx_ol("nok")
except ConnectionError as e:
    print(f"  ❌ oxirgi xato: {e}; so'rovlar: {server.sorovlar}, uxlashlar: {soat.uxlashlar}")

print("\n=== 3. Ro'yxatda yo'q istisno qayta urinilmaydi ===")
server = SoxtaServer([ValueError("noto'g'ri mahsulot kodi")])
try:
    narx_ol("???")
except ValueError as e:
    print(f"  ❌ {e}; so'rovlar: {server.sorovlar}")

print("\n=== 4. Tezlik chegarasi: 10 soniyada 3 ta ===")
xabar_chegarasi = TezlikChegarasi(3, davr=10)


@xabar_chegarasi
def xabar_yubor(matn):
    return f"yuborildi: {matn}"


for i, oraliq in enumerate([0, 1, 1, 1, 7, 1], start=1):
    soat.t += oraliq
    try:
        print(f"  t={soat():>4}s  {xabar_yubor(f'#{i}')}")
    except TezlikXatosi as e:
        print(f"  t={soat():>4}s  ❌ {e}")

print("\n=== 5. Argument turlari va dekoratsiya paytidagi xato ===")


@tekshir(summa=int, valyuta=str)
def tolov(karta, summa, valyuta="UZS"):
    return f"{karta}: {summa:,} {valyuta}"


print(f"  {tolov('8600', 150_000)}")
for args, kwargs in [(("8600", "150000"), {}), (("8600", 5), {"valyuta": 840})]:
    try:
        tolov(*args, **kwargs)
    except TypeError as e:
        print(f"  ❌ {e}")
try:
    @tekshir(sumam=int)
    def tolov2(karta, summa):
        pass
except ValueError as e:
    print(f"  ❌ import paytida: {e}")
try:
    qayta_urin(0)
except ValueError as e:
    print(f"  ❌ fabrika sozlamani tekshirdi: {e}")

print("\n=== 6. Tartib: chegara qayta urinishdan tashqarida yoki ichkarida ===")
for nom, tashqi_chegara in [("@chegara ustida @qayta_urin", True), ("@qayta_urin ustida @chegara", False)]:
    soat.t += 100
    chegara = TezlikChegarasi(3, davr=10)
    server = SoxtaServer([ConnectionError("a"), ConnectionError("b"), "ok"])

    def sorov():
        return server.javob("hisobot")

    if tashqi_chegara:
        f = chegara(qayta_urin(3, kutish=0.1)(sorov))
    else:
        f = qayta_urin(3, kutish=0.1)(chegara(sorov))
    f()
    print(f"  {nom}: chegaradan {len(chegara.vaqtlar)} ta joy ishlatildi")

Natijaning muhim qismi:

text
=== 1. Ikki uzilishdan keyin muvaffaqiyat ===
      narx_ol: ConnectionError → 0.5s kutib, 2-urinish
      narx_ol: TimeoutError → 1.0s kutib, 3-urinish
  olma: 12 000 so'm
  so'rovlar: 3, uxlashlar: [0.5, 1.0], soat: 1.5s

=== 2. Hamma urinishlar muvaffaqiyatsiz ===
      narx_ol: ConnectionError → 0.5s kutib, 2-urinish
      narx_ol: ConnectionError → 1.0s kutib, 3-urinish
      narx_ol: ConnectionError → 2.0s kutib, 4-urinish
  ❌ oxirgi xato: 4; so'rovlar: 4, uxlashlar: [0.5, 1.0, 2.0]

=== 3. Ro'yxatda yo'q istisno qayta urinilmaydi ===
  ❌ noto'g'ri mahsulot kodi; so'rovlar: 1

=== 4. Tezlik chegarasi: 10 soniyada 3 ta ===
  t= 5.0s  yuborildi: #1
  t= 6.0s  yuborildi: #2
  t= 7.0s  yuborildi: #3
  t= 8.0s  ❌ xabar_yubor: chegara 3/10s, 7.0s kuting
  t=15.0s  yuborildi: #5
  t=16.0s  yuborildi: #6

=== 5. Argument turlari va dekoratsiya paytidagi xato ===
  8600: 150,000 UZS
  ❌ tolov: summa int bo'lishi kerak, str berildi ('150000')
  ❌ tolov: valyuta str bo'lishi kerak, int berildi (840)
  ❌ import paytida: @tekshir: tolov2 da bunday parametr yo'q: ['sumam']
  ❌ fabrika sozlamani tekshirdi: urinishlar >= 1 bo'lishi kerak, berildi: 0

=== 6. Tartib: chegara qayta urinishdan tashqarida yoki ichkarida ===
      sorov: ConnectionError → 0.1s kutib, 2-urinish
      sorov: ConnectionError → 0.2s kutib, 3-urinish
  @chegara ustida @qayta_urin: chegaradan 1 ta joy ishlatildi
      sorov: ConnectionError → 0.1s kutib, 2-urinish
      sorov: ConnectionError → 0.2s kutib, 3-urinish
  @qayta_urin ustida @chegara: chegaradan 3 ta joy ishlatildi

Nima ko'rsatdi: 2.3, 2.7, 2.8-bo'limlar va 10.8-darsdagi tartib qoidasi: chegara tashqarida bo'lsa, bitta mantiqiy so'rov bitta joy oladi; ichkarida bo'lsa — har urinish alohida hisoblanadi.


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

Noto'g'ri fikr To'g'risi
"@qayta_urin(3) — maxsus sintaksis" Oddiy chaqiruv ifodasi; natijasi dekorator
"Fabrika har chaqiruvda ishlaydi" Bir marta — @ ifodasi baholanganda
"Dekorator argumenti chaqiruv paytida o'qiladi" Import paytida hisoblangan qiymat saqlanadi
"Bir fabrikadan bezatilgan funksiyalarning holati alohida" Fabrika qavatidagi holat umumiy
"Qavssiz @fabrika darhol xato beradi" Jim o'tadi; xato birinchi chaqiruvda
"Qavsli/qavssiz naqsh har doim ishlaydi" Birinchi sozlama chaqiriladigan bo'lsa — noaniq
"Sinf asosidagi dekorator boshqacha ishlaydi" Xuddi shu uch qavat: __init__, __call__, o'rovchi
"Noto'g'ri sozlama faqat chaqiruvda aniqlanadi" Dekorator qavatida importda tekshirish mumkin

6. Keng tarqalgan xatolar va yechimlari

1. Fabrikada return dekorator ni unutish

python
def qayta_urin(n):
    def dekorator(f):
        ...
        return orovchi
                                # ❌ @qayta_urin(3) → None(f) → TypeError: 'NoneType' object is not callable

def qayta_urin(n):
    def dekorator(f):
        ...
        return orovchi
    return dekorator            # ✅

2. Qavsni unutish

python
@qayta_urin                     # ❌ yukla = dekorator; yukla() → TypeError
def yukla(): ...

@qayta_urin(3)                  # ✅
def yukla(): ...

3. Konfiguratsiyani dekorator argumenti sifatida

python
@vaqt_chegarasi(settings.TIMEOUT)          # ❌ import paytidagi qiymat

@vaqt_chegarasi(lambda: settings.TIMEOUT)  # ✅ chaqiruv paytida o'qiladi

4. Fabrikada o'zgaruvchan standart qiymat

python
def kesh(saqlash={}): ...       # ❌ hamma bezatilgan funksiyalar bitta lug'at

def kesh(saqlash=None):
    def dekorator(f):
        joy = {} if saqlash is None else saqlash   # ✅ dekorator qavatida yangi

5. Umumiy bo'lishi kerak bo'lgan holatni dekorator qavatiga qo'yish

python
def tezlik(soni):
    def dekorator(f):
        vaqtlar = deque()       # ❌ har funksiyaga alohida — API umumiy chegarasi buziladi

Umumiy chegara — fabrika qavatida yoki sinf nusxasida, va bitta nusxa hamma funksiyaga qo'llanadi.

6. O'rovchi ichida fabrika argumentini qayta tayinlash

python
def qayta_urin(urinishlar):
    def dekorator(f):
        def orovchi(*args):
            while urinishlar > 0:
                urinishlar -= 1         # ❌ UnboundLocalError — urinishlar lokal bo'lib qoldi

Lokal nusxa oling: qolgan = urinishlar va qolgan ni kamaytiring. (nonlocal bu yerda noto'g'ri — hamma chaqiruvlar bitta hisoblagichni kamaytirardi.)

7. Qavsli/qavssiz dekoratorda sozlamalar pozitsion

python
def jurnal(f=None, daraja="INFO"): ...
@jurnal("XATO")                 # ❌ "XATO" funksiya o'rniga tushdi

def jurnal(f=None, /, *, daraja="INFO"): ...    # ✅ + callable tekshiruvi

8. Sozlamani faqat chaqiruvda tekshirish

python
@tekshir(sumam=int)             # ❌ imlo xatosi — tekshiruv jim ishlamaydi

# ✅ dekorator qavatida: inspect.signature bilan parametr nomlarini tekshiring

7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 7.3-dars (o'tilgan): o'zgaruvchan standart qiymat
  • 7.6-dars (o'tilgan): / va * — qavsli/qavssiz dekorator imzosi
  • 7.9-dars (o'tilgan): nonlocal — qavatlar orasidagi holat
  • 10.8-dars (o'tilgan): @ ochilishi, qatlamlar tartibi
  • 10.11-dars: functools.wraps(funksiya) — o'zi ham argumentli dekorator
  • 10.12-dars: closure va kechikkan bog'lanish
  • 10.13-dars: lru_cache(maxsize=...) — qavsli va qavssiz
  • 17-qism: pytest.mark.parametrize(...), mock.patch(...)
  • 20-qism: @app.get("/yo'l"), @router.post(...) — marshrut fabrikalari

8. Eng yaxshi amaliyotlar

  1. Uch qavatni nomlang: fabrika — dekorator nomi, ichkilari — dekorator va orovchi. O'qiydigan odam qavatni darhol taniydi.

  2. Sozlamalar nomli bo'lsin — @qayta_urin(urinishlar=5) o'qilishi @qayta_urin(5) dan aniqroq.

  3. Holatni ongli ravishda joylashtiring: umumiy — fabrikada, funksiyaga xos — dekoratorda, chaqiruvga xos — o'rovchida.

  4. O'zgaradigan konfiguratsiya uchun chaqiriladigan obyekt yoki sozlama obyektini uzating.

  5. Sozlamalarni fabrikada, parametr nomlarini dekorator qavatida tekshiring — xato importda chiqsin.

  6. Holat tashqaridan boshqarilishi kerak bo'lsa — sinf asosidagi dekorator.

  7. Qavsli/qavssiz naqshni faqat birinchi sozlama chaqiriladigan bo'lmasa ishlating.

  8. Testlarda soat, uxlash va tasodifni argument sifatida uzating — uxla=, soat= — dekorator deterministik tekshiriladi.


9. Amaliy topshiriq

Vazifa 1: Natijani bashorat qiling

python
def d(x):
    print("d", x, end=" ")
    def dek(f):
        print("dek", end=" ")
        def w(*a):
            return f(*a) + x
        return w
    return dek

def e(x):
    def dek(f):
        return lambda: f() * x
    return dek

def kesh(saqla={}):
    def dek(f):
        def w(n):
            if n not in saqla:
                saqla[n] = f(n)
            return saqla[n]
        return w
    return dek

def j(f=None, *, p="*"):
    def dek(g):
        return lambda: p + g()
    return dek if f is None else dek(f)

class Kop:
    def __init__(self, k):
        self.k = k
    def __call__(self, f):
        return lambda *a: f(*a) * self.k
1.  @d(1)
    def f():
        return 10
    print(f())
2.  @d(1)
    @d(2)
    def g():
        return 0
    print(g())
3.  dek5 = d(5)
    @dek5
    def a():
        return 1
    @dek5
    def b():
        return 2
    print(a(), b())
4.  @e
    def h():
        return 1
    print(callable(h), h.__name__)
5.  @e
    def h():
        return 1
    try:
        h()
    except TypeError:
        print("TypeError")
6.  @kesh()
    def kv(n):
        return n * n
    @kesh()
    def kub(n):
        return n ** 3
    print(kv(2), kub(2))
7.  n = 3
    @e(n)
    def k():
        return 2
    n = 100
    print(k())
8.  @j
    def s():
        return "a"
    print(s())
9.  @j(p="#")
    def t():
        return "b"
    print(t())
10. @j()
    def u():
        return "c"
    print(u())
11. try:
        @j("!")
        def v():
            return "d"
    except TypeError:
        print("TypeError")
12. @Kop(3)
    def w(x):
        return x + 1
    print(w(1))
Javoblar
  1. d 1 dek 11 — fabrika, dekorator, keyin chaqiruv
  2. d 1 d 2 dek dek 3 — fabrikalar yuqoridan pastga, dekoratorlar pastdan yuqoriga
  3. d 5 dek dek 6 7 — bitta fabrika chaqiruvi, ikki dekoratsiya
  4. True dek — qavs unutildi: h endi dekoratorning o'zi
  5. TypeError — dek() ga f berilmadi
  6. 4 4 — ikkala funksiya bitta standart lug'atni ishlatdi
  7. 6 — argument def paytida hisoblangan (3)
  8. *a — qavssiz shakl
  9. #b
  10. *c — bo'sh qavs ham ishlaydi
  11. TypeError — j("!") dekorator emas, lambda qaytardi; lambda v ni argument sifatida qabul qilmaydi
  12. 6 — (1 + 1) * 3

Vazifa 2: Xatolarni tuzating

python
1.  def takrorla(n):
        def dekorator(f):
            def orovchi(*args):
                natijalar = []
                while n > 0:
                    natijalar.append(f(*args))
                    n -= 1
                return natijalar
            return orovchi

2.  def chegara(soni, davr):
        def dekorator(f):
            vaqtlar = []
            def orovchi(*args):
                ...
            return orovchi
        return dekorator
    api_chegara = 100
    @chegara(api_chegara, 60)
    def royxat(): ...
    @chegara(api_chegara, 60)
    def profil(): ...      # API ikkala endpoint uchun UMUMIY 100/min beradi

3.  def jurnal(f=None, daraja="INFO"):
        def dekorator(g): ...
        return dekorator if f is None else dekorator(f)
    @jurnal("DEBUG")
    def hisobla(): ...

4.  @qayta_urin
    def yukla(): ...

5.  def ruxsat(rollar=[]):
        def dekorator(f):
            rollar.append("admin")
            def orovchi(user, *args):
                if user.rol not in rollar:
                    raise PermissionError
                return f(user, *args)
            return orovchi
        return dekorator
Javoblar
python
1.  def takrorla(n):
        def dekorator(f):
            def orovchi(*args):
                return [f(*args) for _ in range(n)]     # n ni o'zgartirmaymiz
            return orovchi
        return dekorator                                # fabrika dekoratorni qaytarishi shart

2.  umumiy = chegara(100, 60)       # bitta fabrika chaqiruvi — fabrika qavatiga ko'chirilgan holat
    @umumiy
    def royxat(): ...
    @umumiy
    def profil(): ...
    # va chegara ichida vaqtlar = deque() dekorator qavatidan fabrika qavatiga ko'chiriladi

3.  def jurnal(f=None, /, *, daraja="INFO"):
        if f is not None and not callable(f):
            raise TypeError("daraja ni nomi bilan bering: @jurnal(daraja=...)")
        ...
    @jurnal(daraja="DEBUG")
    def hisobla(): ...

4.  @qayta_urin()                   # yoki @qayta_urin(3)
    def yukla(): ...

5.  def ruxsat(*rollar):
        def dekorator(f):
            ruxsatlilar = {*rollar, "admin"}     # har dekoratsiyada yangi to'plam
            def orovchi(user, *args):
                if user.rol not in ruxsatlilar:
                    raise PermissionError(user.rol)
                return f(user, *args)
            return orovchi
        return dekorator

Vazifa 3: @vaqt_chegarasi

vaqt_chegarasi(soniya, /, *, istisno=TimeoutError, soat=time.monotonic) yozing:

  1. Funksiya soniya dan uzoq ishlasa, natija qaytarilmasdan istisno ko'tarilsin (funksiya to'xtatilmaydi — faqat tugagach tekshiriladi)
  2. soniya chaqiriladigan bo'lishi ham mumkin — chaqiruv paytida o'qiladi
  3. soniya <= 0 — fabrikada ValueError
  4. Soxta soat bilan testlar yozing

Vazifa 4: Qavsli va qavssiz @kesh

kesh(funksiya=None, /, *, maksimal=None, kalit=None) yozing:

  1. @kesh, @kesh(), @kesh(maksimal=100) ishlasin
  2. maksimal to'lganda eng eski yozuv o'chirilsin
  3. kalit — argumentlardan kesh kalitini yasovchi funksiya (faqat nomli — nega?)
  4. f.kesh_tozala() va f.kesh_holati() → {"topildi": ..., "topilmadi": ..., "hajm": ...}
  5. @kesh(len) shaklidagi xato ishlatishni aniqlab, tushunarli xabar bering

Vazifa 5: @ruxsat va rollar ierarxiyasi

  1. @ruxsat("moderator") — foydalanuvchi roli kamida moderator bo'lsin (mehmon < foydalanuvchi < moderator < admin)
  2. Noma'lum rol nomi — import paytida ValueError
  3. Bezatilgan funksiyaning birinchi parametri foydalanuvchi deb nomlanmagan bo'lsa — import paytida TypeError
  4. ruxsat.royxat() — qaysi funksiyaga qaysi rol kerakligini chiqarsin (ro'yxatga olish fabrika darajasida)

Vazifa 6: Sinf asosidagi Metrika

Metrika(nom) nusxasi dekorator fabrikasi bo'lsin:

  1. @metrika va @metrika(teglar={"endpoint": "profil"}) — ikkalasi ishlasin
  2. Har chaqiruvning davomiyligi, muvaffaqiyati va teglarini yozsin
  3. metrika.hisobot() — funksiya va teglar bo'yicha: soni, xatolar ulushi, o'rtacha va maksimal vaqt
  4. Bitta Metrika nusxasi bir nechta funksiyada, bir nechta nusxa bitta funksiyada ishlashini tekshiring

Vazifa 7: O'ylash

Argumentli dekorator Python'da uch qavatli ichma-ich funksiya yoki __init__ + __call__ li sinf bo'ladi. Nega Python dizaynerlari @dekorator(arg) def f ni "dekoratorga qo'shimcha argument" deb emas, "chaqiruv natijasi dekorator" deb aniqlashdi? Bu tanlovning qanday afzallik va kamchiliklari bor, va amalda uch qavatning murakkabligini qanday kamaytirish mumkin?

Javob

Qisqa javob: @ dan keyin turgan narsa — oddiy ifoda, uning qiymati chaqiriladigan obyekt bo'lishi kerak. Bu yagona qoida hamma holatni qamraydi: nom, atribut, chaqiruv, indeks. Argumentlar uchun maxsus qoida kiritilmagani sababli til sodda qoldi, lekin narxi — dasturchi uchun qo'shimcha qavat va qavsni unutish kabi jim xatolar. Murakkablikni sinf, functools.partial asosidagi naqsh, yordamchi "dekorator yasovchi" va, eng muhimi, yaxshi nomlash kamaytiradi.

1. Tarix: PEP 318

Dekoratorlar Python 2.4 da (PEP 318) qo'shilgan. O'shanda grammatika faqat nuqtali nom va ixtiyoriy bitta chaqiruv ga ruxsat berardi: @nom, @modul.nom, @nom(argumentlar). Chaqiruv bor-yo'qligi alohida ma'noga ega emas edi — u shunchaki ifodaning bir qismi: nom(argumentlar) avval baholanadi, natija dekorator sifatida ishlatiladi. Python 3.9 da PEP 614 cheklovni butunlay olib tashladi va @ dan keyin istalgan ifodaga ruxsat berdi — bu mantiqning tabiiy davomi.

2. Afzalliklari

a) Yagona qoida. @X def f → f = X(f). X qanday yozilganidan qat'i nazar. O'rganish uchun bitta model yetadi.

b) Kompozitsiya. Fabrika natijasi — oddiy qiymat, uni saqlash, uzatish, qayta ishlatish mumkin:

python
tarmoq_urinishi = qayta_urin(5, istisnolar=(ConnectionError, TimeoutError))

@tarmoq_urinishi
def narx(): ...

@tarmoq_urinishi
def ombor(): ...

c) Holat joylashuvi aniq. Fabrika chaqiruvi — umumiy holat uchun tabiiy joy (Misol 2, 1-bo'lim).

d) Istalgan chaqiriladigan obyekt. Sinf nusxasi, functools.partial, metod — hammasi dekorator bo'la oladi.

3. Kamchiliklari

a) Ikki xil "shakl" bir xil ko'rinadi. @kesh va @kesh() — biri to'g'ri, biri xato, qaysi biri ekanini faqat implementatsiya aytadi.

b) Jim xatolar. Qavs unutilsa, funksiya dekoratorga aylanadi va xato faqat chaqiruvda chiqadi (Misol 1, 5-bo'lim).

c) Uch qavat o'qish qiyin. Ayniqsa nonlocal, functools.wraps va tekshiruvlar qo'shilganda.

d) Qavsli/qavssiz naqsh noaniq. Birinchi sozlama chaqiriladigan bo'lsa, dekorator nima kelganini aniq bila olmaydi (Misol 3, 3-bo'lim).

4. Boshqa tillar bilan qisqa solishtiruv

Java annotatsiyalarida (@Retry(maxAttempts = 5)) argumentlar — metama'lumot maydonlari, ularni framework o'qiydi (10.8-dars, Vazifa 7). U yerda "qavs unutish" muammosi yo'q, lekin annotatsiya hech narsa bajarmaydi. Python yondashuvi kuchliroq, lekin dasturchidan ko'proq intizom talab qiladi.

5. Murakkablikni kamaytirish usullari

a) Sinf asosidagi fabrika — qavatlar metodlarga bo'linadi:

python
class qayta_urin:
    def __init__(self, urinishlar=3, *, istisnolar=(Exception,)):
        self.urinishlar, self.istisnolar = urinishlar, istisnolar

    def __call__(self, funksiya):
        @functools.wraps(funksiya)
        def orovchi(*args, **kwargs):
            ...
        return orovchi

Ichma-ich chuqurlik bitta kamayadi, holat atributlarda.

b) functools.partial bilan qavsli/qavssiz naqsh:

python
def jurnal(funksiya=None, /, *, daraja="INFO"):
    if funksiya is None:
        return functools.partial(jurnal, daraja=daraja)
    @functools.wraps(funksiya)
    def orovchi(*args, **kwargs):
        ...
    return orovchi

Uch qavat ikkiga tushadi: @jurnal(daraja="X") sozlamalar bilan qisman qo'llangan jurnal ni qaytaradi, u keyin funksiyani oladi.

c) "Dekorator yasovchi" yordamchi:

python
def sozlanuvchi(dekorator_funksiyasi):
    """dekorator_funksiyasi(funksiya, *args, **sozlamalar) yozing — qavsli va qavssiz shakl avtomatik."""
    def fabrika(funksiya=None, /, **sozlamalar):
        if funksiya is None:
            return lambda f: dekorator_funksiyasi(f, **sozlamalar)
        return dekorator_funksiyasi(funksiya, **sozlamalar)
    return fabrika

Uchinchi tomon kutubxonalari (masalan, wrapt) shu g'oyani yanada rivojlantiradi: metodlar, deskriptorlar va imzolarni to'g'ri saqlaydigan dekorator yasash.

d) Nomlash. Qavatlarga bir xil, ma'noli nomlar (fabrika → dekorator → orovchi) — uch qavatni o'qishni sezilarli osonlashtiradi.

e) Argumentli dekoratorga ehtiyoj bormi? Ba'zan oddiy funksiya chaqiruvi yoki argumentsiz dekorator + sozlama obyekti aniqroq. Faqat bitta joyda ishlatiladigan argumentli dekorator — ortiqcha abstraksiya.

6. Xulosa

  1. @ dan keyin — ifoda; argumentli dekorator shu qoidadan kelib chiqadi, alohida mexanizm emas
  2. Afzalligi — yagona model, kompozitsiya, holat uchun aniq joy
  3. Kamchiligi — qavs unutish kabi jim xatolar va o'qilishi qiyin uch qavat
  4. Sinf, functools.partial va yordamchi yasovchilar qavatlar sonini kamaytiradi
  5. Eng yaxshi himoya — nomli sozlamalar, importdagi tekshiruvlar va izchil nomlash

Nimani mustahkamlaydi: 2.1–2.9-bo'limlar.


Xulosa

Bu darsda argumentli dekoratorlarning ichki tuzilishini o'rgandik.

Eng muhim uch fikr:

  1. @fabrika(x) def f — bu f = fabrika(x)(f). Fabrika dekorator qaytaradi, dekorator o'rovchi qaytaradi. Fabrika va dekorator import paytida bir marta ishlaydi, faqat o'rovchi — har chaqiruvda. Shuning uchun dekorator argumentlari import paytida hisoblanadi: o'zgaruvchan konfiguratsiyani chaqiriladigan obyekt sifatida uzating, qavs unutilsa esa xato jim o'tib, faqat chaqiruvda chiqadi.

  2. Holat qavatga bog'liq. Fabrika qavatidagi o'zgaruvchilar shu fabrika chaqiruvidan bezatilgan barcha funksiyalar uchun umumiy, dekorator qavatidagilar har funksiyaga alohida, o'rovchidagilar har chaqiruvga alohida. Fabrikadagi o'zgaruvchan standart qiymat hamma dekoratsiyalarni bog'lab qo'yadi — None va dekorator qavatida yangi obyekt yarating.

  3. Dekoratorni xavfsiz va qulay qiling. Qavsli/qavssiz naqsh funksiya=None, /, * imzosi va callable tekshiruvi bilan ishlaydi, lekin birinchi sozlama o'zi funksiya bo'lsa noaniq bo'ladi. Sinf asosidagi fabrika holatni ochiq atributlarda saqlaydi. Sozlamalar va parametr nomlarini fabrika va dekorator qavatlarida tekshiring — xato ishlab chiqarishda emas, importda chiqsin.

Keyingi darsda klass dekoratorlarini o'rganamiz: @dataclass kabi dekoratorlar klassni qanday o'zgartiradi, klassni o'rash va klassga metod qo'shish farqi, hamda meros va metaklasslar bilan solishtirish.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
10.9-dars: Argumentli dekorator — IlmHamroh