IlmHamroh
Python kursi/Ilgor til imkoniyatlari8/17-dars27 daqiqa
Mundarija (22)

10.8-dars: Dekorator — tushuncha

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


1. Kirish va motivatsiya

7.11-darsda dekoratorni "funksiya oladi, funksiya qaytaradi" deb ta'rifladik va bir nechta misol yozdik. Endi real loyihadan bir parcha:

python
ilova = Ilova()

@faqat_admin
@ilova.buyruq
def foydalanuvchini_ochir(ism):
    ...

Kod ishlaydi, testlar o'tadi. Faqat bitta muammo bor: istalgan foydalanuvchi bu buyruqni chaqira oladi. Admin tekshiruvi umuman ishlamaydi.

Sababi — ikki qator tartibi. @ilova.buyruq pastda turgani uchun u asl funksiyani ro'yxatga oldi. @faqat_admin esa undan keyin qo'llandi va o'ralgan funksiyani faqat foydalanuvchini_ochir nomiga bog'ladi. Ilova buyruqlarni o'z ro'yxatidan chaqiradi — o'ralmagan funksiyadan.

Dekoratorni ishlatish oson, lekin to'g'ri ishlatish uchun @ aynan nima qilishini, qachon bajarilishini va qaysi tartibda qo'llanishini aniq bilish kerak.

Bu darsda:

  • @ sintaksisi nimaga ochiladi va qachon bajariladi
  • Bir nechta dekorator: qo'llanish tartibi va bajarilish tartibi ("piyoz" modeli)
  • Dekorator nima qaytarishi mumkin
  • O'rovchi funksiya anatomiyasi
  • Tuzoqlar: rekursiya, generator funksiyalar, metodlar, staticmethod/classmethod
  • Dekorator qachon to'g'ri vosita, qachon emas

Argumentli dekoratorlar — 10.9-darsda, klass dekoratorlari — 10.10-darsda, functools.wraps — 10.11-darsda, closure'lar chuqur — 10.12-darsda.


2. Nazariya — chuqur tushuntirish

2.1. @ nimaga ochiladi

python
@dekorator
def f():
    ...

Bu taxminan quyidagiga teng:

python
def f():
    ...
f = dekorator(f)

"Taxminan" — chunki ikkita nozik farq bor:

  1. @ shaklida o'ralmagan funksiya f nomiga hech qachon bog'lanmaydi. Dekorator ishlayotgan paytda f nomi hali yo'q (yoki eski qiymatga ega)
  2. Dekorator ifodasi def dan oldin baholanadi

Aniq tartib:

python
@A
@B
def f(): ...
  1. A ifodasi baholanadi
  2. B ifodasi baholanadi
  3. Funksiya obyekti yaratiladi (nom hali bog'lanmagan)
  4. B(funksiya) chaqiriladi
  5. A(natija) chaqiriladi
  6. Oxirgi natija f nomiga bog'lanadi

Ya'ni ifodalar yuqoridan pastga baholanadi, qo'llanish pastdan yuqoriga: f = A(B(f)).

2.2. Dekorator def paytida bajariladi

Dekorator funksiya chaqirilganda emas, aniqlanganda ishlaydi. Modul darajasidagi funksiya uchun bu — modul import qilinganda:

python
royxat = []

def royxatga_ol(f):
    royxat.append(f.__name__)       # ⭐ import paytida bajariladi
    return f

@royxatga_ol
def hisobot(): ...

print(royxat)                       # ['hisobot'] — hisobot() hali chaqirilmagan

Bundan ikki xulosa:

  • Ro'yxatga olish (marshrutlar, buyruqlar, plaginlar) — dekoratorning tabiiy vazifasi
  • Dekoratorda og'ir ish (tarmoq, fayl, ma'lumotlar bazasi) qilish — importni sekinlashtiradi va import paytidagi xatolarga olib keladi

Klass ichidagi metodlar uchun — klass tanasi bajarilayotganda; funksiya ichidagi funksiya uchun — tashqi funksiya har chaqirilganda.

2.3. Dekorator nima qaytarishi mumkin

Dekorator — shunchaki chaqiriladigan obyekt, natijasi esa istalgan narsa bo'lishi mumkin:

Qaytaradi Misol Natija
O'sha funksiyani Ro'yxatga olish, atribut qo'shish Funksiya o'zgarmaydi
Yangi o'rovchi funksiya Jurnal, vaqt o'lchash, kesh Xatti-harakat kengayadi
Boshqa turdagi obyekt property, staticmethod, functools.cache Nom boshqa narsaga bog'lanadi
None return unutilgan Nom None ga bog'lanadi

Oxirgisi eng keng tarqalgan xato: dekorator return ni unutsa, funksiya import paytida jim yo'qoladi, xato esa birinchi chaqiruvda — 'NoneType' object is not callable bo'lib chiqadi.

2.4. PEP 614 — istalgan ifoda

Python 3.9 dan @ dan keyin istalgan ifoda yozish mumkin (avval faqat nuqtali nom va bitta chaqiruv mumkin edi):

python
@tugmalar[0].bosilganda
def saqlash(): ...

@(jurnal if DEBUG else lambda f: f)
def hisobla(): ...

2.5. O'rovchi funksiya anatomiyasi

python
def jurnal(funksiya):                        # 1. def paytida bir marta
    def orovchi(*args, **kwargs):            # 2. har chaqiruvda
        print(f"→ {funksiya.__name__}{args}")
        try:
            natija = funksiya(*args, **kwargs)    # 3. asl funksiya
        except Exception as e:
            print(f"✗ {type(e).__name__}")
            raise                            # 4. ⭐ xatoni yutmaymiz
        print(f"← {natija!r}")
        return natija                        # 5. ⭐ natijani qaytaramiz
    return orovchi                           # 6. ⭐ o'rovchini qaytaramiz

To'g'ri o'rovchining besh talabi:

  1. *args, **kwargs — istalgan argumentlarni o'zgartirmay uzatadi
  2. return — asl natijani qaytaradi
  3. raise — istisnolarni yutmaydi (ataylab qilinmasa)
  4. Ichki funksiyada funksiya — closure orqali eslab qolinadi (10.12-dars)
  5. Metama'lumotni ko'chiradi — functools.wraps (10.11-dars)

Oxirgisisiz nima yo'qoladi:

python
jurnal_bilan.__name__       # 'orovchi'
jurnal_bilan.__doc__        # None
inspect.signature(...)      # (*args, **kwargs)

2.6. Bir nechta dekorator — "piyoz" modeli

python
@A
@B
@C
def f(): ...

f = A(B(C(f))) — A eng tashqi qatlam. Chaqiruv qatlamlardan tashqaridan ichkariga o'tadi va natija ichkaridan tashqariga qaytadi:

chaqiruv → A oldin → B oldin → C oldin → f → C keyin → B keyin → A keyin → natija

Tartib ma'noni o'zgartiradi:

Tartib Ma'nosi
@qayta_urin ustida @jurnal Har bir urinish jurnalga yoziladi
@jurnal ustida @qayta_urin Jurnalda bitta yozuv — hamma urinishlar ichkarida
@kesh ustida @hisobla Faqat kesh o'tkazib yuborgan chaqiruvlar sanaladi
@hisobla ustida @kesh Hamma chaqiruvlar sanaladi
@faqat_admin ustida @ilova.buyruq ro'yxatda tekshiruvsiz funksiya
@ilova.buyruq ustida @faqat_admin ro'yxatda tekshiruvli funksiya

Qoida: ro'yxatga oluvchi dekorator — eng tashqarida (eng yuqorida), chunki u qolgan hamma qatlamlarni o'z ichiga olgan funksiyani saqlashi kerak.

2.7. Tuzoqlar

A) Rekursiya o'rovchidan o'tadi.

python
@jurnal
def fib(n):
    return n if n < 2 else fib(n - 1) + fib(n - 2)

fib nomi ichkarida ham o'rovchiga ishora qiladi (nom global qidiriladi, LEGB). Shuning uchun har bir rekursiv chaqiruv jurnalga yoziladi. Kesh uchun bu foydali (har qadam keshlanadi), jurnal va vaqt o'lchash uchun — shovqin.

B) Generator funksiya.

python
@vaqt_olch
def satrlar(yol):
    with open(yol) as f:
        yield from f

Generator funksiya chaqirilganda hech narsa bajarilmaydi — faqat generator obyekti qaytadi (10.3-dars). O'rovchi shu lahzani o'lchaydi — ~0 soniya. Haqiqiy ish keyin, iteratsiyada bo'ladi. Xuddi shu gap try/except li o'rovchiga ham tegishli: generator ichidagi xato o'rovchidan tashqarida ko'tariladi.

C) Metodlar.

self oddiy birinchi argument — *args orqali o'tadi, o'rovchi metodlarda ham ishlaydi. Lekin staticmethod va classmethod bilan tartib muhim:

python
class K:
    @classmethod
    @jurnal                 # ✅ jurnal oddiy funksiyani o'raydi
    def yarat(cls): ...

    @jurnal
    @classmethod            # ❌ K.yarat() → TypeError: 'classmethod' object is not callable
    def yarat2(cls): ...

classmethod, staticmethod, property — deskriptorlar (10.17-dars), ular klassda eng tashqi qatlam bo'lishi kerak. Aks holda ular funksiya ichiga yashirinib qoladi va o'z vazifasini bajarmaydi.

D) Narxi.

*args, **kwargs li o'rovchi har chaqiruvga o'nlab nanosekund qo'shadi (Python 3.14 da ~50 ns). Kichik funksiya siklda millionlab chaqirilsa — seziladi.

2.8. Dekorator qachon to'g'ri vosita

Ko'ndalang vazifalar (cross-cutting concerns) — biznes mantiqqa aloqasi yo'q, lekin ko'p funksiyada takrorlanadigan ishlar:

  • jurnal, vaqt o'lchash, metrika
  • kesh
  • qayta urinish
  • huquqlarni tekshirish
  • tranzaksiya
  • ro'yxatga olish (marshrut, buyruq, plagin, test)

Noto'g'ri qo'llanish:

  • funksiyaning asosiy mantiqini dekoratorga yashirish — o'quvchi funksiyaga qarab nima qilishini bilolmaydi
  • faqat bitta joyda ishlatiladigan dekorator — oddiy funksiya chaqiruvi aniqroq
  • argumentlarni sezilmas tarzda o'zgartiradigan dekorator — imzo va haqiqat mos kelmaydi

3. Tez ma'lumotnoma

Sintaksis

python
@A
@B
def f(): ...          # f = A(B(f))
Ifodalar baholanishi:   A, B        (yuqoridan pastga)
Qo'llanish:             B, keyin A  (pastdan yuqoriga)
Chaqiruvda:             A → B → f → B → A
Qachon:                 def bajarilganda (modul uchun — importda)

O'rovchi shabloni

python
def dekorator(funksiya):
    def orovchi(*args, **kwargs):
        # oldin
        natija = funksiya(*args, **kwargs)
        # keyin
        return natija
    return orovchi

Tartib qoidalari

ro'yxatga oluvchi         → eng yuqorida
classmethod / staticmethod / property → eng yuqorida (klassda)
kesh ↔ hisoblagich, jurnal ↔ qayta urinish → ma'no o'zgaradi

Tuzoqlar

return unutildi   → nom None ga bog'lanadi
rekursiya         → har ichki chaqiruv o'rovchidan o'tadi
generator         → o'rovchi faqat yaratilishni ko'radi
metama'lumot      → __name__ = 'orovchi' (10.11 da wraps)

4. Batafsil misollar

Misol 1 — @ sintaksisini kuzatish

python
"""Dekorator ifodasi qachon baholanadi, qachon qo'llanadi, nom qachon bog'lanadi."""


def kuzat(yorliq):
    """Kuzatuv uchun dekorator yaratuvchi (argumentli dekoratorlar — 10.9-darsda)."""
    print(f"  ifoda baholandi: {yorliq}")

    def dekorator(funksiya):
        print(f"  qo'llandi: {yorliq}({funksiya.__name__})")
        return funksiya

    return dekorator


print("=== 1. Tartib: ifodalar yuqoridan pastga, qo'llanish pastdan yuqoriga ===")
print("  def dan oldin")


@kuzat("A")
@kuzat("B")
@kuzat("C")
def vazifa():
    return "bajarildi"


print(f"  def dan keyin — vazifa() hali chaqirilmagan, lekin hammasi ishladi")

print("\n=== 2. Dekorator ishlayotganda nom hali bog'lanmagan ===")


def nomni_tekshir(funksiya):
    nom = funksiya.__name__
    print(f"  globals() da {nom!r} bormi: {nom in globals()}")
    return funksiya


@nomni_tekshir
def yangi_funksiya():
    pass


print(f"  def dan keyin: {'yangi_funksiya' in globals()}")


def yangi_funksiya_2():
    pass


print("  qo'lda yozilgan shaklda esa:")
yangi_funksiya_2 = nomni_tekshir(yangi_funksiya_2)

print("\n=== 3. Dekorator istalgan narsa qaytarishi mumkin ===")
royxat = []


def royxatga_ol(funksiya):
    royxat.append(funksiya.__name__)
    return funksiya                              # o'sha funksiya


def ikki_marta(funksiya):
    def orovchi(*args, **kwargs):
        funksiya(*args, **kwargs)
        return funksiya(*args, **kwargs)
    return orovchi                               # yangi funksiya


@royxatga_ol
def salomlash(ism):
    print(f"    salom, {ism}")
    return ism.upper()


@ikki_marta
def chiq(matn):
    print(f"    {matn}")


@(lambda funksiya: funksiya())                  # darhol chaqirib, natijasini saqlaydi
def SOZLAMA():
    return {"til": "uz", "sahifa": 20}


print(f"  royxat: {royxat}, salomlash turi: {type(salomlash).__name__}")
chiq("ikki marta chiqadi")
print(f"  SOZLAMA endi funksiya emas: {type(SOZLAMA).__name__} = {SOZLAMA}")

print("\n=== 4. ⚠️ return unutilgan dekorator ===")


def yarim_dekorator(funksiya):
    funksiya.tekshirildi = True                  # return yo'q


@yarim_dekorator
def muhim_hisob():
    return 42


print(f"  import paytida xato yo'q, muhim_hisob = {muhim_hisob}")
try:
    muhim_hisob()
except TypeError as e:
    print(f"  birinchi chaqiruvda: TypeError: {e}")

print("\n=== 5. PEP 614: istalgan ifoda ===")


class Tugma:
    def __init__(self, nom):
        self.nom = nom
        self.ishlovchilar = []

    def bosilganda(self, funksiya):
        self.ishlovchilar.append(funksiya)
        return funksiya

    def bos(self):
        for f in self.ishlovchilar:
            f(self.nom)


tugmalar = [Tugma("Saqlash"), Tugma("Chiqish")]
DEBUG = False


@tugmalar[0].bosilganda
def saqla(nom):
    print(f"    '{nom}' bosildi — saqlanmoqda")


@(ikki_marta if DEBUG else lambda f: f)
def hisobla():
    print("    hisoblandi")


tugmalar[0].bos()
hisobla()

Natijaning muhim qismi:

text
=== 1. Tartib: ifodalar yuqoridan pastga, qo'llanish pastdan yuqoriga ===
  def dan oldin
  ifoda baholandi: A
  ifoda baholandi: B
  ifoda baholandi: C
  qo'llandi: C(vazifa)
  qo'llandi: B(vazifa)
  qo'llandi: A(vazifa)
  def dan keyin — vazifa() hali chaqirilmagan, lekin hammasi ishladi

=== 2. Dekorator ishlayotganda nom hali bog'lanmagan ===
  globals() da 'yangi_funksiya' bormi: False
  def dan keyin: True
  qo'lda yozilgan shaklda esa:
  globals() da 'yangi_funksiya_2' bormi: True

=== 3. Dekorator istalgan narsa qaytarishi mumkin ===
  royxat: ['salomlash'], salomlash turi: function
    ikki marta chiqadi
    ikki marta chiqadi
  SOZLAMA endi funksiya emas: dict = {'til': 'uz', 'sahifa': 20}

=== 4. ⚠️ return unutilgan dekorator ===
  import paytida xato yo'q, muhim_hisob = None
  birinchi chaqiruvda: TypeError: 'NoneType' object is not callable

=== 5. PEP 614: istalgan ifoda ===
    'Saqlash' bosildi — saqlanmoqda
    hisoblandi

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

Misol 2 — O'rovchi anatomiyasi va tuzoqlar

python
"""To'g'ri o'rovchi, nima yo'qoladi, rekursiya, generator va metodlar."""

import inspect

chuqurlik = 0


def jurnal(funksiya):
    def orovchi(*args, **kwargs):
        global chuqurlik
        belgi = "  " * chuqurlik
        qism = [repr(a) for a in args] + [f"{k}={v!r}" for k, v in kwargs.items()]
        print(f"    {belgi}→ {funksiya.__name__}({', '.join(qism)})")
        chuqurlik += 1
        try:
            natija = funksiya(*args, **kwargs)
        except Exception as e:
            print(f"    {belgi}✗ {type(e).__name__}: {e}")
            raise
        finally:
            chuqurlik -= 1
        print(f"    {belgi}← {natija!r}")
        return natija
    return orovchi


print("=== 1. Argumentlar, natija va istisno o'zgarmay o'tadi ===")


@jurnal
def bol(a, b, *, butun=False):
    """a ni b ga bo'ladi."""
    return a // b if butun else a / b


bol(7, 2)
bol(7, 2, butun=True)
try:
    bol(1, 0)
except ZeroDivisionError:
    print("    istisno tashqarida ushlandi ✅")

print("\n=== 2. Nima yo'qoldi (10.11-darsda tuzatamiz) ===")
print(f"  __name__:  {bol.__name__!r}")
print(f"  __doc__:   {bol.__doc__!r}")
print(f"  imzo:      {inspect.signature(bol)}")

print("\n=== 3. ⚠️ Rekursiya o'rovchidan o'tadi ===")


@jurnal
def fib(n):
    return n if n < 2 else fib(n - 1) + fib(n - 2)


fib(3)

sanoq = {"jami": 0}


def hisobla(funksiya):
    def orovchi(*args):
        sanoq["jami"] += 1
        return funksiya(*args)
    return orovchi


@hisobla
def fib2(n):
    return n if n < 2 else fib2(n - 1) + fib2(n - 2)


fib2(20)
print(f"  fib2(20): o'rovchi {sanoq['jami']:,} marta chaqirildi — har rekursiv qadam")

print("\n=== 4. ⚠️ Generator funksiya: o'rovchi faqat yaratilishni ko'radi ===")


def bosqich(funksiya):
    def orovchi(*args, **kwargs):
        print("    [o'rovchi] boshlandi")
        try:
            return funksiya(*args, **kwargs)
        except ValueError:
            print("    [o'rovchi] ValueError ushlandi")
        finally:
            print("    [o'rovchi] tugadi")
    return orovchi


@bosqich
def sonlar(qatorlar):
    for q in qatorlar:
        print(f"    [generator] {q!r} o'qilmoqda")
        yield int(q)


gen = sonlar(["1", "2", "uch"])
print(f"  qaytgan: {type(gen).__name__}")
try:
    for x in gen:
        print(f"    olindi: {x}")
except ValueError as e:
    print(f"  ⚠️ ValueError o'rovchidan tashqarida: {e}")

print("\n=== 5. Metodlar, staticmethod va classmethod ===")


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


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

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

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

    @oddiy
    @classmethod
    def xato_tartib(cls):
        return cls(0)

    @staticmethod
    @oddiy
    def valyuta():
        return "UZS"

    @oddiy
    @staticmethod
    def xato_statik():
        return "UZS"


h = Hisob(500)
print(f"  oddiy metod, self *args orqali: {h.qoldiq()}")
print(f"  @classmethod @oddiy:  {type(Hisob.bosh()).__name__}")
print(f"  @staticmethod @oddiy: {Hisob.valyuta()}, nusxadan: {h.valyuta()}")
for nom, chaqir in [("@oddiy @classmethod", lambda: Hisob.xato_tartib()),
                    ("@oddiy @staticmethod (nusxadan)", lambda: h.xato_statik())]:
    try:
        chaqir()
    except TypeError as e:
        print(f"  ❌ {nom}: TypeError: {e}")
print(f"  @oddiy @staticmethod (klassdan): {Hisob.xato_statik()}   ⚠️ faqat klassdan ishlaydi")

Natijaning muhim qismi:

text
=== 1. Argumentlar, natija va istisno o'zgarmay o'tadi ===
    → bol(7, 2)
    ← 3.5
    → bol(7, 2, butun=True)
    ← 3
    → bol(1, 0)
    ✗ ZeroDivisionError: division by zero
    istisno tashqarida ushlandi ✅

=== 2. Nima yo'qoldi (10.11-darsda tuzatamiz) ===
  __name__:  'orovchi'
  __doc__:   None
  imzo:      (*args, **kwargs)

=== 3. ⚠️ Rekursiya o'rovchidan o'tadi ===
    → fib(3)
      → fib(2)
        → fib(1)
        ← 1
        → fib(0)
        ← 0
      ← 1
      → fib(1)
      ← 1
    ← 2
  fib2(20): o'rovchi 21,891 marta chaqirildi — har rekursiv qadam

=== 4. ⚠️ Generator funksiya: o'rovchi faqat yaratilishni ko'radi ===
    [o'rovchi] boshlandi
    [o'rovchi] tugadi
  qaytgan: generator
    [generator] '1' o'qilmoqda
    olindi: 1
    [generator] '2' o'qilmoqda
    olindi: 2
    [generator] 'uch' o'qilmoqda
  ⚠️ ValueError o'rovchidan tashqarida: invalid literal for int() with base 10: 'uch'

=== 5. Metodlar, staticmethod va classmethod ===
  oddiy metod, self *args orqali: 500
  @classmethod @oddiy:  Hisob
  @staticmethod @oddiy: UZS, nusxadan: UZS
  ❌ @oddiy @classmethod: TypeError: 'classmethod' object is not callable
  ❌ @oddiy @staticmethod (nusxadan): TypeError: Hisob.xato_statik() takes 0 positional arguments but 1 was given
  @oddiy @staticmethod (klassdan): UZS   ⚠️ faqat klassdan ishlaydi

Nima ko'rsatdi: 2.5, 2.7-bo'limlar.

Misol 3 — Piyoz: qatlamlar tartibi ma'noni o'zgartiradi

python
"""Bir xil dekoratorlar, boshqa tartib — boshqa natija."""

import random


def qatlam(nom):
    """Kuzatuv uchun: oldin va keyin chop etadigan dekorator yaratadi."""
    def dekorator(funksiya):
        def orovchi(*args, **kwargs):
            print(f"    {nom} oldin")
            natija = funksiya(*args, **kwargs)
            print(f"    {nom} keyin")
            return natija
        return orovchi
    return dekorator


print("=== 1. Chaqiruv qatlamlardan qanday o'tadi ===")


@qatlam("A")
@qatlam("B")
@qatlam("C")
def yadro():
    print("    >>> yadro <<<")


yadro()

jurnal_yozuvlari = []


def jurnal(funksiya):
    def orovchi(*args, **kwargs):
        try:
            natija = funksiya(*args, **kwargs)
            jurnal_yozuvlari.append(f"{funksiya.__name__}: ok")
            return natija
        except Exception as e:
            jurnal_yozuvlari.append(f"{funksiya.__name__}: {type(e).__name__}")
            raise
    return orovchi


def qayta_urin(funksiya):
    def orovchi(*args, **kwargs):
        for urinish in range(1, 4):
            try:
                return funksiya(*args, **kwargs)
            except ConnectionError:
                if urinish == 3:
                    raise
    return orovchi


def beqaror_tarmoq():
    rng = random.Random(7)                       # har chaqiruvda bir xil ketma-ketlik
    def sorov():
        if rng.random() < 0.6:
            raise ConnectionError("uzildi")
        return "javob"
    sorov.__name__ = "sorov"
    return sorov


print("\n=== 2. jurnal va qayta_urin ===")
a = jurnal(qayta_urin(beqaror_tarmoq()))         # @jurnal @qayta_urin
b = qayta_urin(jurnal(beqaror_tarmoq()))         # @qayta_urin @jurnal
for nom, f in [("@jurnal ustida @qayta_urin", a), ("@qayta_urin ustida @jurnal", b)]:
    jurnal_yozuvlari.clear()
    try:
        natija = f()
    except ConnectionError as e:
        natija = f"ConnectionError: {e}"
    print(f"  {nom}: {natija}")
    print(f"    jurnal: {jurnal_yozuvlari}")

print("\n=== 3. kesh va hisoblagich ===")


def kesh(funksiya):
    saqlangan = {}
    def orovchi(n):
        if n not in saqlangan:
            saqlangan[n] = funksiya(n)
        return saqlangan[n]
    return orovchi


def hisoblagich(funksiya):
    def orovchi(*args):
        orovchi.soni += 1
        return funksiya(*args)
    orovchi.soni = 0
    return orovchi


def kvadrat(n):
    return n * n


tashqi_hisob = hisoblagich(kesh(kvadrat))        # @hisoblagich @kesh
ichki_hisob_orovchi = hisoblagich(kvadrat)
ichki_hisob = kesh(ichki_hisob_orovchi)          # @kesh @hisoblagich
for n in [3, 3, 4, 3, 4, 5]:
    tashqi_hisob(n)
    ichki_hisob(n)
print(f"  @hisoblagich ustida @kesh: {tashqi_hisob.soni} — barcha so'rovlar")
print(f"  @kesh ustida @hisoblagich: {ichki_hisob_orovchi.soni} — faqat haqiqiy hisoblashlar")

print("\n=== 4. Tekshiruv va o'zgartirish tartibi ===")


def musbat_bolsin(funksiya):
    def orovchi(x):
        if x <= 0:
            raise ValueError(f"musbat bo'lishi kerak: {x}")
        return funksiya(x)
    return orovchi


def modul_ol(funksiya):
    def orovchi(x):
        return funksiya(abs(x))
    return orovchi


def ildiz(x):
    return x ** 0.5


tartib1 = musbat_bolsin(modul_ol(ildiz))         # avval tekshiradi, keyin abs
tartib2 = modul_ol(musbat_bolsin(ildiz))         # avval abs, keyin tekshiradi
for nom, f in [("@musbat_bolsin @modul_ol", tartib1), ("@modul_ol @musbat_bolsin", tartib2)]:
    try:
        print(f"  {nom}: f(-9) = {f(-9)}")
    except ValueError as e:
        print(f"  {nom}: ValueError: {e}")

Natijaning muhim qismi:

text
=== 1. Chaqiruv qatlamlardan qanday o'tadi ===
    A oldin
    B oldin
    C oldin
    >>> yadro <<<
    C keyin
    B keyin
    A keyin

=== 2. jurnal va qayta_urin ===
  @jurnal ustida @qayta_urin: javob
    jurnal: ['orovchi: ok']
  @qayta_urin ustida @jurnal: javob
    jurnal: ['sorov: ConnectionError', 'sorov: ConnectionError', 'sorov: ok']

=== 3. kesh va hisoblagich ===
  @hisoblagich ustida @kesh: 6 — barcha so'rovlar
  @kesh ustida @hisoblagich: 3 — faqat haqiqiy hisoblashlar

=== 4. Tekshiruv va o'zgartirish tartibi ===
  @musbat_bolsin @modul_ol: ValueError: musbat bo'lishi kerak: -9
  @modul_ol @musbat_bolsin: f(-9) = 3.0

Nima ko'rsatdi: 2.6-bo'lim — f = A(B(C(f))), chaqiruv tashqaridan ichkariga. 2-bo'limdagi 'orovchi: ok' yozuviga e'tibor bering: tashqi jurnal ichki dekoratorning o'rovchisini ko'rdi va asl nom yo'qoldi — 10.11-darsdagi functools.wraps aynan shu uchun.

Misol 4 — Amaliy: buyruqlar tizimi va ro'yxatga olish tuzog'i

Kichik bank konsol ilovasi. Buyruqlar dekorator bilan ro'yxatga olinadi, huquq tekshiruvi, tranzaksiya va audit — alohida dekoratorlar.

python
"""Ro'yxatga olish, huquq, tranzaksiya va audit dekoratorlari; noto'g'ri tartib xavfsizlik teshigini ochadi."""

import copy


class Ilova:
    def __init__(self):
        self.buyruqlar = {}
        self.foydalanuvchi = None
        self.hisoblar = {"Aziz": 1000, "Bobur": 200}
        self.audit = []

    def buyruq(self, funksiya):
        """Ro'yxatga oluvchi dekorator: nom — funksiya nomi."""
        self.buyruqlar[funksiya.__name__.replace("_", "-")] = funksiya
        return funksiya

    def bajar(self, foydalanuvchi, qator):
        self.foydalanuvchi = foydalanuvchi
        nom, *argumentlar = qator.split()
        try:
            natija = self.buyruqlar[nom](self, *argumentlar)
            return f"✅ {natija}"
        except KeyError:
            return f"❓ noma'lum buyruq: {nom}"
        except (PermissionError, ValueError) as e:
            return f"❌ {type(e).__name__}: {e}"


ADMINLAR = {"admin"}


def faqat_admin(funksiya):
    def orovchi(ilova, *args):
        if ilova.foydalanuvchi not in ADMINLAR:
            raise PermissionError(f"{ilova.foydalanuvchi} admin emas")
        return funksiya(ilova, *args)
    orovchi.__name__ = funksiya.__name__         # nomni qo'lda saqlaymiz (10.11 da wraps)
    return orovchi


def tranzaksiyali(funksiya):
    def orovchi(ilova, *args):
        nusxa = copy.deepcopy(ilova.hisoblar)
        try:
            return funksiya(ilova, *args)
        except Exception:
            ilova.hisoblar = nusxa
            raise
    orovchi.__name__ = funksiya.__name__
    return orovchi


def audit(funksiya):
    def orovchi(ilova, *args):
        try:
            natija = funksiya(ilova, *args)
            ilova.audit.append((ilova.foydalanuvchi, funksiya.__name__, "ok"))
            return natija
        except Exception as e:
            ilova.audit.append((ilova.foydalanuvchi, funksiya.__name__, type(e).__name__))
            raise
    orovchi.__name__ = funksiya.__name__
    return orovchi


ilova = Ilova()


@ilova.buyruq
def balans(ilova, ism):
    return f"{ism}: {ilova.hisoblar[ism]:,}"


@ilova.buyruq
@audit
@tranzaksiyali
def otkaz(ilova, kimdan, kimga, summa):
    summa = int(summa)
    ilova.hisoblar[kimdan] -= summa
    ilova.hisoblar[kimga] = ilova.hisoblar.get(kimga, 0) + summa
    if ilova.hisoblar[kimdan] < 0:
        raise ValueError(f"{kimdan} hisobida mablag' yetarli emas")
    return f"{kimdan} → {kimga}: {summa:,}"


@ilova.buyruq                                    # ✅ eng yuqorida
@audit
@faqat_admin
def hisob_ochir(ilova, ism):
    del ilova.hisoblar[ism]
    return f"{ism} hisobi o'chirildi"


@faqat_admin                                     # ❌ ro'yxatga oluvchidan yuqorida
@ilova.buyruq
def hammasini_nolla(ilova):
    for ism in ilova.hisoblar:
        ilova.hisoblar[ism] = 0
    return "barcha hisoblar nollandi"


print("=== 1. Ro'yxatga olingan buyruqlar (import paytida) ===")
print(f"  {list(ilova.buyruqlar)}")

print("\n=== 2. Oddiy foydalanuvchi ===")
for qator in ["balans Aziz", "otkaz Aziz Bobur 300", "otkaz Bobur Aziz 9000",
              "hisob-ochir Bobur", "kredit Aziz"]:
    print(f"  aziz> {qator:24} {ilova.bajar('aziz', qator)}")
print(f"  hisoblar: {ilova.hisoblar}   ⭐ muvaffaqiyatsiz o'tkazma orqaga qaytarildi")

print("\n=== 3. ❌ Tartib xatosi: tekshiruv chetlab o'tildi ===")
print(f"  aziz> hammasini-nolla        {ilova.bajar('aziz', 'hammasini-nolla')}")
print(f"  hisoblar: {ilova.hisoblar}")
try:
    ilova.foydalanuvchi = "aziz"
    hammasini_nolla(ilova)
except PermissionError as e:
    print(f"  to'g'ridan-to'g'ri chaqiruvda esa tekshiruv bor: PermissionError: {e}")
print(f"  ro'yxatdagi funksiya o'ralganmi: {ilova.buyruqlar['hammasini-nolla'] is not hammasini_nolla}")

print("\n=== 4. Admin ===")
ilova.hisoblar = {"Aziz": 700, "Bobur": 500}
print(f"  admin> hisob-ochir Bobur     {ilova.bajar('admin', 'hisob-ochir Bobur')}")
print(f"  hisoblar: {ilova.hisoblar}")

print("\n=== 5. Audit jurnali ===")
for yozuv in ilova.audit:
    print(f"  {yozuv}")

Natijaning muhim qismi:

text
=== 1. Ro'yxatga olingan buyruqlar (import paytida) ===
  ['balans', 'otkaz', 'hisob-ochir', 'hammasini-nolla']

=== 2. Oddiy foydalanuvchi ===
  aziz> balans Aziz              ✅ Aziz: 1,000
  aziz> otkaz Aziz Bobur 300     ✅ Aziz → Bobur: 300
  aziz> otkaz Bobur Aziz 9000    ❌ ValueError: Bobur hisobida mablag' yetarli emas
  aziz> hisob-ochir Bobur        ❌ PermissionError: aziz admin emas
  aziz> kredit Aziz              ❓ noma'lum buyruq: kredit
  hisoblar: {'Aziz': 700, 'Bobur': 500}   ⭐ muvaffaqiyatsiz o'tkazma orqaga qaytarildi

=== 3. ❌ Tartib xatosi: tekshiruv chetlab o'tildi ===
  aziz> hammasini-nolla        ✅ barcha hisoblar nollandi
  hisoblar: {'Aziz': 0, 'Bobur': 0}
  to'g'ridan-to'g'ri chaqiruvda esa tekshiruv bor: PermissionError: aziz admin emas
  ro'yxatdagi funksiya o'ralganmi: True

=== 4. Admin ===
  admin> hisob-ochir Bobur     ✅ Bobur hisobi o'chirildi
  hisoblar: {'Aziz': 700}

=== 5. Audit jurnali ===
  ('aziz', 'otkaz', 'ok')
  ('aziz', 'otkaz', 'ValueError')
  ('aziz', 'hisob_ochir', 'PermissionError')
  ('admin', 'hisob_ochir', 'ok')

Nima ko'rsatdi: 2.2, 2.6-bo'limlar — ro'yxatga olish import paytida bo'ladi va u o'zidan pastdagi hamma narsani saqlaydi; tartib xatosi testda sezilmasligi mumkin, chunki funksiyani to'g'ridan-to'g'ri chaqirganda tekshiruv ishlaydi.


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

Noto'g'ri fikr To'g'risi
"Dekorator funksiya chaqirilganda ishlaydi" def bajarilganda — modul uchun import paytida
"@A @B def f — avval A qo'llanadi" f = A(B(f)): avval B, keyin A
"Dekorator ichida funksiyani nomi bilan chaqirish mumkin" Nom hali bog'lanmagan
"Dekorator albatta funksiya qaytaradi" Istalgan obyekt — hatto None (xato)
"@ dan keyin faqat nom yoziladi" 3.9 dan istalgan ifoda
"Rekursiv funksiyaning ichki chaqiruvlari o'rovchini chetlab o'tadi" Nom orqali chaqirilgani uchun o'rovchidan o'tadi
"Generator funksiyaning vaqtini o'rovchi o'lchaydi" Faqat generator yaratilishini
"Dekoratorlar tartibi faqat uslub masalasi" Ma'noni, hatto xavfsizlikni o'zgartiradi

6. Keng tarqalgan xatolar va yechimlari

1. return orovchi ni unutish

python
def jurnal(f):
    def orovchi(*args, **kwargs):
        return f(*args, **kwargs)
                                    # ❌ f endi None

def jurnal(f):
    def orovchi(*args, **kwargs):
        return f(*args, **kwargs)
    return orovchi                  # ✅

2. O'rovchida return ni unutish

python
def orovchi(*args, **kwargs):
    print("chaqirildi")
    f(*args, **kwargs)              # ❌ natija yo'qoldi — har doim None

def orovchi(*args, **kwargs):
    print("chaqirildi")
    return f(*args, **kwargs)       # ✅

3. Dekoratorni chaqirib qo'yish

python
@jurnal()                           # ❌ TypeError: jurnal() missing 1 required positional argument
def f(): ...

@jurnal                             # ✅ argumentsiz dekorator qavssiz
def f(): ...

4. Ro'yxatga oluvchi dekorator pastda

python
@faqat_admin
@app.route("/ochir")                # ❌ marshrut tekshiruvsiz funksiyani saqladi
def ochir(): ...

@app.route("/ochir")                # ✅ eng yuqorida
@faqat_admin
def ochir(): ...

5. classmethod/staticmethod/property ni ichkariga qo'yish

python
@jurnal
@classmethod                        # ❌ 'classmethod' object is not callable
def yarat(cls): ...

@classmethod                        # ✅
@jurnal
def yarat(cls): ...

6. Generator funksiyaga oddiy o'rovchi

python
@vaqt_olch
def oqi(yol):
    yield from open(yol)            # ❌ o'lchov ~0 s — faqat yaratilish

@vaqt_olch
def oqi_hammasi(yol):
    return list(oqi(yol))           # ✅ haqiqiy ishni bajaradigan funksiyani o'lchang

Generatorlar uchun alohida dekorator yozish ham mumkin — o'rovchining o'zi generator bo'lib, yield from qiladi.

7. Dekoratorda import paytida og'ir ish

python
def ulanishli(f):
    db = connect("postgres://...")  # ❌ har importda ulanish, test muhitida yiqiladi
    def orovchi(*args):
        return f(db, *args)
    return orovchi

Resursni o'rovchi ichida, birinchi chaqiruvda oling.

8. Rekursiv funksiyaga shovqinli dekorator

python
@jurnal
def fib(n): ...                     # ❌ fib(30) — 2.7 mln jurnal yozuvi

def fib(n): ...
@jurnal
def fib_hisobla(n):                 # ✅ faqat tashqi chaqiruv
    return fib(n)

7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 7.11-dars (o'tilgan): funksiya — obyekt, yuqori tartibli funksiyalar
  • 8.15–8.17-darslar (o'tilgan): @property, @classmethod, @dataclass — dekoratorlar
  • 10.3-dars (o'tilgan): generator chaqirilganda tana bajarilmaydi
  • 10.9–10.13-darslar: argumentli va klass dekoratorlari, wraps, closure, functools.cache
  • 10.17-dars: deskriptorlar — classmethod va property nega tashqarida bo'lishi kerak
  • 17-qism: pytest.mark, pytest.fixture, mock.patch — dekoratorlar
  • 20, 22-qismlar: FastAPI va Flask marshrutlari, Depends — ro'yxatga olish dekoratorlari

8. Eng yaxshi amaliyotlar

  1. O'rovchi argumentlarni *args, **kwargs bilan o'zgartirmay uzatsin, natijani qaytarsin, istisnoni yutmasin.

  2. Ro'yxatga oluvchi dekoratorlar va deskriptorlar (classmethod, property) — eng yuqorida.

  3. Tartibni ongli tanlang va izohlang — kesh/hisoblagich, jurnal/qayta urinish, tekshiruv/o'zgartirish.

  4. Dekorator import paytida yengil bo'lsin. Og'ir ishni o'rovchi ichida qiling.

  5. Generator va rekursiv funksiyalarni o'rashdan oldin o'ylang.

  6. Dekorator — ko'ndalang vazifa uchun. Asosiy mantiqni yashirmang.

  7. Metama'lumotni saqlang — functools.wraps (10.11-dars).

  8. Muhim dekoratorlarni ro'yxatdan chaqirib test qiling, faqat to'g'ridan-to'g'ri emas — tartib xatolari shunda ko'rinadi.


9. Amaliy topshiriq

Vazifa 1: Natijani bashorat qiling

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

def ikki(f):
    def w(*a):
        return f(*a) * 2
    return w
1.  @d("A")
    @d("B")
    def f():
        print("f", end=" ")
    f()
    print()
2.  @ikki
    @ikki
    def g():
        return 3
    print(g())
3.  def yoz(f):
        print("yoz", end=" ")
        return f
    print("oldin", end=" ")
    @yoz
    def h(): pass
    print("keyin")
4.  def unut(f):
        f.x = 1
    @unut
    def k(): pass
    print(k)
5.  @ikki
    def r(n):
        return 1 if n == 0 else r(n - 1)
    print(r(2))
6.  @ikki
    def s():
        return "ab"
    print(s(), s.__name__)
7.  @(lambda f: f())
    def t():
        return [1, 2]
    print(t)
8.  @ikki
    def gen():
        yield 1
    try:
        gen()
    except TypeError as e:
        print("TypeError")
9.  class K:
        @classmethod
        @ikki
        def m(cls):
            return 5
    print(K.m())
10. class K:
        @ikki
        @staticmethod
        def m():
            return 5
    print(K.m())
11. royxat = []
    def ol(f):
        royxat.append(f)
        return f
    @ikki
    @ol
    def u():
        return 1
    print(u(), royxat[0]())
12. @d("X")
    def v(n):
        return n
    print(v(4))
Javoblar
  1. A B f — A eng tashqi qatlam, birinchi ishlaydi
  2. 12 — 3 * 2 * 2
  3. oldin yoz keyin — dekorator def paytida ishladi
  4. None — dekorator return qilmadi
  5. 8 — har rekursiv chaqiruv ham o'rovchidan o'tadi: 1 → 2 → 4 → 8
  6. abab w — satr ikkiga ko'paytirildi, nom esa w
  7. [1, 2] — t funksiya emas, natija
  8. TypeError — generator obyektini * 2 qilib bo'lmaydi
  9. 10 — to'g'ri tartib
  10. 10 — staticmethod 3.10 dan chaqiriladigan, klassdan chaqirganda ishlaydi
  11. 2 1 — ro'yxatda o'ralmagan funksiya
  12. X 4 — print ichida avval v(4) hisoblanadi va X chiqadi

Vazifa 2: Xatolarni tuzating

python
1.  def vaqt(f):
        def orovchi(*args, **kwargs):
            bosh = time.perf_counter()
            f(*args, **kwargs)
            print(time.perf_counter() - bosh)
        return orovchi

2.  def jim(f):
        def orovchi(*args):
            try:
                return f(*args)
            except Exception:
                print("xato")
        return orovchi

3.  class Foydalanuvchi:
        @jurnal
        @property
        def ism(self):
            return self._ism

4.  @login_kerak
    @app.route("/profil")
    def profil(): ...

5.  @kesh
    def satrlar(yol):
        with open(yol) as f:
            for q in f:
                yield q.strip()
Javoblar
python
1.  def vaqt(f):
        def orovchi(*args, **kwargs):
            bosh = time.perf_counter()
            try:
                return f(*args, **kwargs)           # natija qaytariladi
            finally:
                print(time.perf_counter() - bosh)   # xato bo'lsa ham o'lchaydi
        return orovchi

2.  def jim(f):
        def orovchi(*args):
            try:
                return f(*args)
            except Exception:
                print("xato")
                raise                   # yutish ataylab bo'lmasa — qayta ko'tarish
        return orovchi

3.  class Foydalanuvchi:
        @property                       # deskriptor eng tashqarida
        @jurnal
        def ism(self):
            return self._ism

4.  @app.route("/profil")               # ro'yxatga oluvchi eng tashqarida
    @login_kerak
    def profil(): ...

5.  # Generator obyektini keshlash ma'nosiz — birinchi iteratsiyadan keyin u bo'sh.
    @kesh
    def satrlar(yol):
        with open(yol) as f:
            return tuple(q.strip() for q in f)      # o'zgarmas natijani keshlaymiz

Vazifa 3: Uch dekorator

Argumentsiz dekoratorlar yozing:

  1. @hisobla — chaqiruvlar sonini f.soni atributida saqlaydi
  2. @vaqt_olch — davomiylikni f.oxirgi_vaqt ga yozadi, istisno bo'lsa ham
  3. @bir_marta — funksiyani faqat birinchi chaqiruvda bajaradi, keyin o'sha natijani qaytaradi; birinchi chaqiruv xato bersa, keyingi chaqiruv yana urinib ko'rsin

Har biri uchun: oddiy funksiya, metod va rekursiv funksiyada test yozing. Rekursiyada nima bo'lishini izohlang.

Vazifa 4: Generatorga mos dekorator

@generator_vaqti yozing:

  1. O'ralgan funksiya generator funksiya bo'lsa (inspect.isgeneratorfunction), o'rovchi ham generator bo'lsin va butun iteratsiya vaqtini o'lchasin
  2. Iteratsiya oxirigacha bormasa (break) ham o'lchov yozilsin
  3. Oddiy funksiyalar uchun oddiy o'lchov
  4. Ichki generatorga send() va throw() shaffof o'tsin (10.5-dars)

Vazifa 5: Marshrutizator

Marshrutizator sinfini yozing:

  1. @r.get va @r.post — funksiya nomidan yo'l yasaydi (profil_korish → /profil/korish)
  2. r.chaqir("GET", "/profil/korish", **params)
  3. Ro'yxatga olishda bir xil yo'l ikki marta kelsa — import paytida ValueError
  4. @login_kerak dekoratori va uni noto'g'ri tartibda qo'llashni aniqlaydigan tekshiruv: r.tekshir() — ro'yxatdagi funksiya bilan moduldagi nom bir xil obyekt bo'lmasa, ogohlantirish chiqarsin

Vazifa 6: Dekoratorlar tartibi testi

@jurnal, @kesh, @qayta_urin, @huquq dekoratorlarining 24 xil tartibini avtomatik ishga tushiradigan dastur yozing. Har tartib uchun: jurnal yozuvlari soni, haqiqiy chaqiruvlar soni, huquqsiz foydalanuvchi keshdan natija ola oladimi. Natijani jadval qilib chiqaring va qaysi tartiblar xavfli ekanini aniqlang.

Vazifa 7: O'ylash

Python dekoratorlari, Java annotatsiyalari (@Override, Spring @Transactional), C# atributlari va TypeScript dekoratorlari — hammasi @ belgisi yoki shunga o'xshash sintaksisdan foydalanadi. Ular bir xil narsami? Spring'dagi "this.metod() chaqirilganda @Transactional ishlamaydi" muammosi Python'da bormi?

Javob

Qisqa javob: yo'q. Python dekoratori — def paytida bajariladigan kod, u nomni boshqa obyektga qayta bog'laydi. Java annotatsiyasi va C# atributi — o'zicha hech narsa qilmaydigan metama'lumot; xatti-harakatni ularni o'qiydigan kompilyator yoki framework qo'shadi. TypeScript dekoratorlari Python'ga yaqin — ular ham funksiya — lekin faqat klasslar va ularning a'zolariga qo'llanadi. Spring muammosining sababi proksi, Python'da esa aksincha: nom orqali qilingan ichki chaqiruv ham o'rovchidan o'tadi.

1. Python — bajariladigan qayta bog'lash

python
@jurnal
def f(): ...
# f = jurnal(f)
  • dekorator — oddiy chaqiriladigan obyekt, def paytida chaqiriladi
  • natija nomga bog'lanadi — asl funksiya o'rovchi ichida yashirinadi
  • modul funksiyalari, metodlar va klasslarga qo'llanadi
  • hech qanday framework kerak emas

2. Java annotatsiyalari — metama'lumot

java
@Override
public String toString() { ... }

@Transactional
public void otkaz(...) { ... }

Annotatsiya klass fayliga yoziladigan belgi. @Override ni kompilyator tekshiradi. @Transactional ni esa Spring ishga tushirish paytida refleksiya bilan o'qiydi va obyekt atrofida proksi yaratadi: tashqaridan chaqirilganda proksi tranzaksiyani ochadi, keyin haqiqiy metodni chaqiradi. Spring bo'lmasa, @Transactional hech narsa qilmaydi.

3. C# atributlari

Java annotatsiyalariga o'xshaydi: [Obsolete], [HttpGet("/profil")]. Ular metama'lumot; ASP.NET kabi framework'lar refleksiya bilan o'qib, marshrutlar yoki filtrlar quradi.

4. TypeScript dekoratorlari

TypeScript 5.0 dan boshlab standart (TC39) dekoratorlar qo'llab-quvvatlanadi. Ular Python'ga yaqin: dekorator — klass aniqlanayotganda chaqiriladigan funksiya, metodni almashtirishi mumkin. Farqi: ular faqat klass va klass a'zolariga qo'llanadi — oddiy funksiyaga emas.

5. Spring o'z-o'zini chaqirish muammosi

java
@Service
class HisobXizmati {
    public void ommaviyOtkaz(List<Otkaz> otkazlar) {
        for (var o : otkazlar) this.otkaz(o);    // ⚠️ proksini chetlab o'tadi
    }

    @Transactional
    public void otkaz(Otkaz o) { ... }
}

Tashqi kod xizmat.otkaz(o) ni chaqirsa, u proksi orqali o'tadi — tranzaksiya bor. this.otkaz(o) esa proksini emas, haqiqiy obyektni chaqiradi — tranzaksiya yo'q. Chunki xatti-harakat obyekt atrofiga qo'shilgan, metodning o'ziga emas.

6. Python'da bu muammo yo'q — lekin teskarisi bor

python
class HisobXizmati:
    def ommaviy_otkaz(self, otkazlar):
        for o in otkazlar:
            self.otkaz(o)          # ✅ o'rovchidan o'tadi

    @tranzaksiyali
    def otkaz(self, o): ...

Python'da dekorator klass atributining o'zini almashtiradi — HisobXizmati.otkaz endi o'rovchi. self.otkaz ham, tashqi chaqiruv ham shu o'rovchiga boradi.

Teskari tomoni — Misol 2 dagi rekursiya: ichki chaqiruvni chetlab o'tish kerak bo'lsa (masalan, jurnal shovqinini oldini olish), buni ataylab qilish kerak — ichki yordamchi funksiya yoki __wrapped__ (10.11-dars) orqali.

7. Taqqoslash

Python Java/C# TypeScript
Tabiati Bajariladigan kod Metama'lumot Bajariladigan kod
Kim ishga tushiradi Til, def paytida Kompilyator yoki framework Til, klass aniqlanganda
Nimaga qo'llanadi Funksiya, metod, klass Klass, metod, maydon, parametr... Klass va a'zolari
O'z-o'zini chaqirish O'rovchidan o'tadi Proksi bo'lsa — chetlab o'tadi Metod almashtirilsa — o'tadi
Framework'siz foyda To'liq Faqat kompilyator tekshiruvlari To'liq

8. Amaliy xulosa

Python dasturchisi @ ni ko'rganda "bu yerda nom qayta bog'landi" deb o'ylashi kerak — bu kod va uni o'qib, qadamma-qadam kuzatish mumkin. Java/Spring dan kelgan dasturchi uchun muhim farq: Python'da "sehrli" framework yo'q, proksi ham yo'q — lekin tartib, rekursiya va import vaqti kabi masalalar bevosita dasturchi zimmasida.

9. Xulosa

  1. Python dekoratori — def paytida bajariladigan chaqiruv va nomni qayta bog'lash
  2. Java/C# annotatsiyalari — metama'lumot; xatti-harakatni framework proksi yoki kod generatsiyasi bilan qo'shadi
  3. TypeScript dekoratorlari tabiatan Python'ga yaqin, lekin faqat klasslar uchun
  4. Spring'ning o'z-o'zini chaqirish muammosi proksidan kelib chiqadi; Python'da nom orqali chaqiruv doim o'rovchidan o'tadi
  5. Shuning uchun Python'da ichki chaqiruvlarni chetlab o'tish — ataylab qilinadigan ish

Nimani mustahkamlaydi: 2.1–2.8-bo'limlar.


Xulosa

Bu darsda dekoratorlarning aniq ishlash mexanizmini o'rgandik.

Eng muhim uch fikr:

  1. @A @B def f — bu f = A(B(f)), va u def paytida bajariladi. Dekorator ifodalari yuqoridan pastga baholanadi, qo'llanish esa pastdan yuqoriga. Modul funksiyalari uchun bu import paytida bo'ladi — shuning uchun ro'yxatga olish tabiiy, og'ir ish esa zararli. Dekorator istalgan narsa qaytarishi mumkin; return unutilsa, funksiya jim None ga aylanadi.

  2. To'g'ri o'rovchi shaffof bo'ladi. Argumentlarni *args, **kwargs bilan o'zgartirmay uzatadi, natijani qaytaradi va istisnoni yutmaydi. Lekin u rekursiv funksiyaning har ichki chaqiruvidan ham o'tadi, generator funksiyada esa faqat generator yaratilishini ko'radi. Metodlarda self oddiy argument sifatida o'tadi, classmethod/staticmethod/property esa eng tashqarida turishi kerak.

  3. Qatlamlar tartibi — ma'no. Chaqiruv tashqi qatlamdan ichkariga o'tadi. Kesh va hisoblagich, jurnal va qayta urinish, tekshiruv va o'zgartirish tartibi almashsa, natija o'zgaradi. Ro'yxatga oluvchi dekorator pastda qolsa, ro'yxatda tekshiruvsiz funksiya saqlanadi — to'g'ridan-to'g'ri chaqiruvda sezilmaydigan xavfsizlik teshigi.

Keyingi darsda argumentli dekoratorlarni o'rganamiz: @qayta_urin(3) qanday qilib uch qavatli funksiyaga aylanadi va dekoratorni ham qavsli, ham qavssiz ishlatiladigan qilib yozish.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
10.8-dars: Dekorator — tushuncha — IlmHamroh