Mundarija (23)
- 1. Kirish va motivatsiya
- 2. Nazariya — chuqur tushuntirish
- 2.1. @f(x) nimaga ochiladi
- 2.2. Uch qavat — uch lahza
- 2.3. Holat qaysi qavatda yashaydi
- 2.4. Argumentlar bir marta hisoblanadi
- 2.5. O'zgaruvchan standart qiymat — yangi ko'rinishda
- 2.6. Qavsli va qavssiz — bir dekorator
- 2.7. Sinf asosidagi argumentli dekorator
- 2.8. Dekoratsiya paytida tekshirish
- 2.9. Standart kutubxonadagi argumentli dekoratorlar
- 3. Tez ma'lumotnoma
- 4. Batafsil misollar
- Misol 1 — Uch qavatni kuzatish
- Misol 2 — Holat qavatlari va import paytidagi tuzoqlar
- Misol 3 — Qavsli va qavssiz, sinf asosida
- Misol 4 — Amaliy: API mijoz uchun dekoratorlar
- 5. To'g'ri va noto'g'ri tushunishlar
- 6. Keng tarqalgan xatolar va yechimlari
- 7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 8. Eng yaxshi amaliyotlar
- 9. Amaliy topshiriq
- Xulosa
10.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:
@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:
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:
@qayta_urin(3)
def yukla(): ..._dekorator = qayta_urin(3) # 1. fabrika chaqiriladi → dekorator qaytadi
yukla = _dekorator(yukla) # 2. dekorator funksiyaga qo'llanadi → o'rovchiShuning uchun argumentli dekorator — dekorator qaytaradigan funksiya (dekorator fabrikasi):
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 dekorator2.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:
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 dekoratorapi = 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 jamiQoida: 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
@qayta_urin(SOZLAMA["urinishlar"]) # import paytida: 2
def yukla(): ...
SOZLAMA["urinishlar"] = 10 # dekorator bilmaydiYechimlar:
- Chaqiriladigan obyekt uzating va o'rovchida chaqiring:
@qayta_urin(lambda: SOZLAMA["urinishlar"]) - Sozlama obyektini uzating va o'rovchida o'qing:
@qayta_urin(sozlama=SOZLAMA) 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:
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:
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) # @jurnalMuhim 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:
callablebo'lmasa — tushunarliTypeError
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
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,
nonlocalkerak 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:
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 dekoratorXato 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
def fabrika(sozlama): # @fabrika(...) — importda
def dekorator(funksiya): # def paytida
def orovchi(*args, **kwargs): # har chaqiruvda
return funksiya(*args, **kwargs)
return orovchi
return dekoratorOchilishi
@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 alohidaQavsli/qavssiz
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'at4. Batafsil misollar
Misol 1 — Uch qavatni kuzatish
"""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:
=== 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
"""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:
=== 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 oldiNima ko'rsatdi: 2.3, 2.4, 2.5-bo'limlar.
Misol 3 — Qavsli va qavssiz, sinf asosida
"""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:
=== 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: 5Nima 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.
"""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:
=== 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 ishlatildiNima 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
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
@qayta_urin # ❌ yukla = dekorator; yukla() → TypeError
def yukla(): ...
@qayta_urin(3) # ✅
def yukla(): ...3. Konfiguratsiyani dekorator argumenti sifatida
@vaqt_chegarasi(settings.TIMEOUT) # ❌ import paytidagi qiymat
@vaqt_chegarasi(lambda: settings.TIMEOUT) # ✅ chaqiruv paytida o'qiladi4. Fabrikada o'zgaruvchan standart qiymat
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 yangi5. Umumiy bo'lishi kerak bo'lgan holatni dekorator qavatiga qo'yish
def tezlik(soni):
def dekorator(f):
vaqtlar = deque() # ❌ har funksiyaga alohida — API umumiy chegarasi buziladiUmumiy chegara — fabrika qavatida yoki sinf nusxasida, va bitta nusxa hamma funksiyaga qo'llanadi.
6. O'rovchi ichida fabrika argumentini qayta tayinlash
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
def jurnal(f=None, daraja="INFO"): ...
@jurnal("XATO") # ❌ "XATO" funksiya o'rniga tushdi
def jurnal(f=None, /, *, daraja="INFO"): ... # ✅ + callable tekshiruvi8. Sozlamani faqat chaqiruvda tekshirish
@tekshir(sumam=int) # ❌ imlo xatosi — tekshiruv jim ishlamaydi
# ✅ dekorator qavatida: inspect.signature bilan parametr nomlarini tekshiring7. 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
Uch qavatni nomlang: fabrika — dekorator nomi, ichkilari —
dekoratorvaorovchi. O'qiydigan odam qavatni darhol taniydi.Sozlamalar nomli bo'lsin —
@qayta_urin(urinishlar=5)o'qilishi@qayta_urin(5)dan aniqroq.Holatni ongli ravishda joylashtiring: umumiy — fabrikada, funksiyaga xos — dekoratorda, chaqiruvga xos — o'rovchida.
O'zgaradigan konfiguratsiya uchun chaqiriladigan obyekt yoki sozlama obyektini uzating.
Sozlamalarni fabrikada, parametr nomlarini dekorator qavatida tekshiring — xato importda chiqsin.
Holat tashqaridan boshqarilishi kerak bo'lsa — sinf asosidagi dekorator.
Qavsli/qavssiz naqshni faqat birinchi sozlama chaqiriladigan bo'lmasa ishlating.
Testlarda soat, uxlash va tasodifni argument sifatida uzating —
uxla=,soat=— dekorator deterministik tekshiriladi.
9. Amaliy topshiriq
Vazifa 1: Natijani bashorat qiling
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
d 1 dek 11— fabrika, dekorator, keyin chaqiruvd 1 d 2 dek dek 3— fabrikalar yuqoridan pastga, dekoratorlar pastdan yuqorigad 5 dek dek 6 7— bitta fabrika chaqiruvi, ikki dekoratsiyaTrue dek— qavs unutildi:hendi dekoratorning o'ziTypeError—dek()gafberilmadi4 4— ikkala funksiya bitta standart lug'atni ishlatdi6— argumentdefpaytida hisoblangan (3)*a— qavssiz shakl#b*c— bo'sh qavs ham ishlaydiTypeError—j("!")dekorator emas, lambda qaytardi; lambdavni argument sifatida qabul qilmaydi6—(1 + 1) * 3
Vazifa 2: Xatolarni tuzating
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 dekoratorJavoblar
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 dekoratorVazifa 3: @vaqt_chegarasi
vaqt_chegarasi(soniya, /, *, istisno=TimeoutError, soat=time.monotonic) yozing:
- Funksiya
soniyadan uzoq ishlasa, natija qaytarilmasdanistisnoko'tarilsin (funksiya to'xtatilmaydi — faqat tugagach tekshiriladi) soniyachaqiriladigan bo'lishi ham mumkin — chaqiruv paytida o'qiladisoniya <= 0— fabrikadaValueError- Soxta soat bilan testlar yozing
Vazifa 4: Qavsli va qavssiz @kesh
kesh(funksiya=None, /, *, maksimal=None, kalit=None) yozing:
@kesh,@kesh(),@kesh(maksimal=100)ishlasinmaksimalto'lganda eng eski yozuv o'chirilsinkalit— argumentlardan kesh kalitini yasovchi funksiya (faqat nomli — nega?)f.kesh_tozala()vaf.kesh_holati()→{"topildi": ..., "topilmadi": ..., "hajm": ...}@kesh(len)shaklidagi xato ishlatishni aniqlab, tushunarli xabar bering
Vazifa 5: @ruxsat va rollar ierarxiyasi
@ruxsat("moderator")— foydalanuvchi roli kamidamoderatorbo'lsin (mehmon < foydalanuvchi < moderator < admin)- Noma'lum rol nomi — import paytida
ValueError - Bezatilgan funksiyaning birinchi parametri
foydalanuvchideb nomlanmagan bo'lsa — import paytidaTypeError 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:
@metrikava@metrika(teglar={"endpoint": "profil"})— ikkalasi ishlasin- Har chaqiruvning davomiyligi, muvaffaqiyati va teglarini yozsin
metrika.hisobot()— funksiya va teglar bo'yicha: soni, xatolar ulushi, o'rtacha va maksimal vaqt- Bitta
Metrikanusxasi 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:
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:
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 orovchiIchma-ich chuqurlik bitta kamayadi, holat atributlarda.
b) functools.partial bilan qavsli/qavssiz naqsh:
def jurnal(funksiya=None, /, *, daraja="INFO"):
if funksiya is None:
return functools.partial(jurnal, daraja=daraja)
@functools.wraps(funksiya)
def orovchi(*args, **kwargs):
...
return orovchiUch qavat ikkiga tushadi: @jurnal(daraja="X") sozlamalar bilan qisman qo'llangan jurnal ni qaytaradi, u keyin funksiyani oladi.
c) "Dekorator yasovchi" yordamchi:
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 fabrikaUchinchi 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
@dan keyin — ifoda; argumentli dekorator shu qoidadan kelib chiqadi, alohida mexanizm emas- Afzalligi — yagona model, kompozitsiya, holat uchun aniq joy
- Kamchiligi — qavs unutish kabi jim xatolar va o'qilishi qiyin uch qavat
- Sinf,
functools.partialva yordamchi yasovchilar qavatlar sonini kamaytiradi - 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:
@fabrika(x) def f— buf = 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.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 —
Noneva dekorator qavatida yangi obyekt yarating.Dekoratorni xavfsiz va qulay qiling. Qavsli/qavssiz naqsh
funksiya=None, /, *imzosi vacallabletekshiruvi 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.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!