Nima uçun dekoratorlar kerak?
Tasavvur qiling, loyihangizda önlab funksiya bor va har birining qança vaqt işlaşini ölçamoqçisiz. Har bir funksiya içiga vaqt ölçaydigan kodni köçirib yoziş — zerikarli va xavfli iş: bir joyda unutasiz, boşqa joyda xato qilasiz. Dekorator aynan şu muammoni hal qiladi: u funksiyani özgartirmasdan, uning atrofiga qöşimça xatti-harakat “örab” beradi.
Dekoratorni tuşuniş uçun avval uçta ğoyani özlaştiriş kerak: funksiyalar oddiy obyekt, funksiya içida funksiya yoziş mumkin va içki funksiya taşqi funksiyaning özgaruvçilarini “eslab qoladi”. Keling, bosqiçma-bosqiç boramiz.
Funksiyalar ham obyekt
Python'da funksiya ham son yoki satr kabi obyekt. Uni özgaruvçiga tenglaştiriş, röyxatga soliş yoki boşqa funksiyaga argument sifatida beriş mumkin. Eʼtibor bering: funksiya nomidan keyin qavs qöymasak, u çaqirilmaydi, balki obyektning özi olinadi.
def salom_ber(ism: str) -> str:
return f"Assalomu alaykum, {ism}!"
xabar = salom_ber # qavs yöq — funksiya çaqirilmadi
print(xabar("Dilnoza"))
print(xabar is salom_ber)
print(type(salom_ber))
print(salom_ber.__name__)
Natija:
Assalomu alaykum, Dilnoza!
True
<class 'function'>
salom_ber
xabar va salom_ber — bitta funksiya obyektiga işora qiluvçi ikki nom. Funksiyaning __name__ atributi esa uning asl nomini saqlaydi; bu keyinroq muhim böladi.
Funksiyani argument sifatida uzatiş
Funksiyani boşqa funksiyaga beriş juda kuçli usul. Masalan, sorted() funksiyasining key parametri aynan şunday işlaydi. Özimiz ham şunday funksiya yozamiz: u narxlar röyxatiga istalgan amalni qöllaydi.
def chegirma(narx: int) -> int:
return narx * 90 // 100
def qqs_qoshish(narx: int) -> int:
return narx * 112 // 100
def hammasiga_qollash(amal, narxlar: list[int]) -> list[int]:
return [amal(n) for n in narxlar]
narxlar = [10_000, 25_000, 48_000]
print(hammasiga_qollash(chegirma, narxlar))
print(hammasiga_qollash(qqs_qoshish, narxlar))
Natija:
[9000, 22500, 43200]
[11200, 28000, 53760]
hammasiga_qollash qaysi amal bajarilişini bilmaydi va bilişi ham şart emas. Bu kodni moslaşuvçan qiladi: yangi amal kerak bölsa, faqat yangi kiçik funksiya yozasiz.
Funksiya qaytaruvçi funksiya va closure
Funksiya içida boşqa funksiya yaratib, uni natija sifatida qaytariş mumkin. Eng qiziq tomoni şundaki, içki funksiya taşqi funksiyaning özgaruvçilarini taşqi funksiya işini tugatgandan keyin ham eslab qoladi. Bunday funksiya closure (yopiq funksiya) deb ataladi.
def kopaytiruvchi(koeffitsiyent: float):
def ichki(son: float) -> float:
return son * koeffitsiyent # taşqi özgaruvçini eslab qoldi
return ichki
dollar_som = kopaytiruvchi(12_650)
evro_som = kopaytiruvchi(13_700)
print(dollar_som(20))
print(evro_som(20))
print(dollar_som.__closure__[0].cell_contents)
Natija:
253000
274000
12650
Içki funksiya taşqi özgaruvçini özgartirmoqçi bölsa, nonlocal kalit sözi kerak. Quyidagi hisoblagiç har çaqirilganda bittaga oşadi:
def hisoblagich_yarat():
soni = 0
def keyingi() -> int:
nonlocal soni
soni += 1
return soni
return keyingi
navbat = hisoblagich_yarat()
print(navbat(), navbat(), navbat())
Natija:
1 2 3
*args va **kwargs: qisqa takror
Dekorator istalgan funksiyani öraşi kerak, şuning uçun u funksiya qanday argumentlar olişini oldindan bilmaydi. Bu yerda *args (pozitsion argumentlar kortej körinişida) va **kwargs (nomlangan argumentlar luğat körinişida) yordamga keladi.
def korsat(*args, **kwargs):
print("args:", args)
print("kwargs:", kwargs)
def uzat(*args, **kwargs):
# olingan hamma narsani boşqa funksiyaga özgarişsiz beramiz
korsat(*args, **kwargs)
uzat(1, 2, viloyat="Samarqand", yil=2026)
Natija:
args: (1, 2)
kwargs: {'viloyat': 'Samarqand', 'yil': 2026}
Birinçi dekoratorimiz
Endi hamma bölaklar tayyor. Dekorator — bu funksiya oladigan va yangi funksiya qaytaradigan funksiya. Yangi funksiya (odatda wrapper yoki orovchi deb nomlanadi) asl funksiyani çaqiradi, lekin oldin va keyin öz işini bajaradi.
def baland_ovoz(funksiya):
def orovchi(*args, **kwargs):
natija = funksiya(*args, **kwargs)
return natija.upper() + "!"
return orovchi
def tabrik(ism: str) -> str:
return f"tuğilgan kuningiz bilan, {ism}"
tabrik = baland_ovoz(tabrik) # qölda “bezaş”
print(tabrik("Sardor"))
Natija:
TUĞILGAN KUNINGIZ BILAN, SARDOR!
tabrik = baland_ovoz(tabrik) qatori eski funksiyani öralgan yangi funksiya bilan almaştirdi. Endi tabrik deb çaqirsak, aslida orovchi işlaydi, u esa içida asl funksiyani çaqiradi.
@ sintaksisi
Yuqoridagi tabrik = baland_ovoz(tabrik) yozuvi biroz noqulay: funksiya nomi uç marta takrorlanadi. Python buning uçun qisqa yozuv beradi — funksiya taʼrifining ustiga @dekorator_nomi yoziladi. Maʼnosi aynan bir xil.
def chiziq_bilan(funksiya):
def orovchi(*args, **kwargs):
print("-" * 20)
natija = funksiya(*args, **kwargs)
print("-" * 20)
return natija
return orovchi
@chiziq_bilan
def chek_chiqar(mahsulot: str, narx: int) -> None:
print(f"{mahsulot}: {narx:,} söm".replace(",", " "))
chek_chiqar("Non", 4_000)
chek_chiqar(mahsulot="Çoy", narx=18_500)
Natija:
--------------------
Non: 4 000 söm
--------------------
--------------------
Çoy: 18 500 söm
--------------------
Bir funksiyaga bir neçta dekorator qöyiş ham mumkin. Ular pastdan yuqoriga qarab qöllanadi: funksiyaga eng yaqin dekorator birinçi bölib öraydi.
functools.wraps: funksiya “pasporti”ni saqlaş
Dekoratorning bitta yaşirin muammosi bor: öralgan funksiya öz nomi va hujjat satrini (docstring) yöqotadi, çunki endi uning örnida içki funksiya turibdi. Bu xatolarni qidirişda va hujjatlar yaratişda çalkaşlik keltirib çiqaradi. Quyidagi misolda içki funksiyani inglizça wrapper deb nomladik — bu ham keng tarqalgan nom.
from functools import wraps
def oddiy(funksiya):
def wrapper(*args, **kwargs):
return funksiya(*args, **kwargs)
return wrapper
def toza(funksiya):
@wraps(funksiya)
def wrapper(*args, **kwargs):
return funksiya(*args, **kwargs)
return wrapper
@oddiy
def ayir():
"""Ayiriş funksiyasi hujjati."""
@toza
def kopaytir():
"""Köpaytiriş funksiyasi hujjati."""
print(ayir.__name__, "|", ayir.__doc__)
print(kopaytir.__name__, "|", kopaytir.__doc__)
Natija:
wrapper | None
kopaytir | Köpaytiriş funksiyasi hujjati.
@wraps(funksiya) asl funksiyaning nomi, hujjati va boşqa maʼlumotlarini wrapperga köçiradi. Şuning uçun har bir dekoratorda wraps işlatişni odat qiling.
Argumentli dekoratorlar
Baʼzan dekoratorni sozlaş kerak: masalan, funksiyani neça marta qayta urinib körişni körsatiş. Buning uçun yana bir qavat qöşiladi: taşqi funksiya sozlamani oladi va dekorator qaytaradi.
from functools import wraps
def qayta_urin(marta: int):
def dekorator(funksiya):
@wraps(funksiya)
def orovchi(*args, **kwargs):
for urinish in range(1, marta + 1):
try:
return funksiya(*args, **kwargs)
except ConnectionError as xato:
print(f"{urinish}-uriniş muvaffaqiyatsiz: {xato}")
print("Barça urinişlar tugadi.")
return orovchi
return dekorator
holat = {"çaqiruv": 0}
@qayta_urin(marta=3)
def server_bilan_boglan() -> str:
holat["çaqiruv"] += 1
if holat["çaqiruv"] < 3:
raise ConnectionError("server javob bermadi")
return "Ulaniş muvaffaqiyatli"
print(server_bilan_boglan())
Natija:
1-uriniş muvaffaqiyatsiz: server javob bermadi
2-uriniş muvaffaqiyatsiz: server javob bermadi
Ulaniş muvaffaqiyatli
@qayta_urin(marta=3) yozuvini ikki bosqiçda öqing: avval qayta_urin(marta=3) çaqiriladi va u dekorator funksiyasini qaytaradi, keyin öşa dekorator funksiyamizga qöllanadi.
Amaliy dekorator: vaqtni ölçaş
Endi kirişda aytilgan vazifaga qaytamiz. Vaqtni aniq ölçaş uçun time.perf_counter() işlatiladi: u eng aniq soatdan foydalanadi va aynan oraliqlarni ölçaş uçun möljallangan.
import time
from functools import wraps
def vaqtni_olcha(funksiya):
@wraps(funksiya)
def orovchi(*args, **kwargs):
boshlanish = time.perf_counter()
natija = funksiya(*args, **kwargs)
sarflandi = time.perf_counter() - boshlanish
print(f"{funksiya.__name__} {sarflandi:.4f} soniya işladi")
return natija
return orovchi
@vaqtni_olcha
def kvadratlar_yigindisi(n: int) -> int:
return sum(i * i for i in range(n))
print(kvadratlar_yigindisi(1_000_000))
Natija:
kvadratlar_yigindisi 0.0479 soniya işladi
333332833333500000
Amaliy dekorator: natijalarni eslab qoliş (keş)
Agar funksiya bir xil argument bilan qayta-qayta çaqirilsa va har safar bir xil natija bersa, natijani eslab qoliş (keşlaş) vaqtni keskin tejaydi. Klassik misol — Fibonaççi sonlari: oddiy rekursiv yeçim bir xil qiymatlarni millionlab marta qayta hisoblaydi.
from functools import wraps
def kesh(funksiya):
xotira: dict = {}
@wraps(funksiya)
def orovchi(*args):
if args not in xotira:
xotira[args] = funksiya(*args)
return xotira[args]
orovchi.xotira = xotira
return orovchi
@kesh
def fibonachchi(n: int) -> int:
if n < 2:
return n
return fibonachchi(n - 1) + fibonachchi(n - 2)
print(fibonachchi(80))
print("Xotiradagi qiymatlar soni:", len(fibonachchi.xotira))
Natija:
23416728348467685
Xotiradagi qiymatlar soni: 81
Bu ğoya şunçalik keng tarqalganki, standart kutubxonada tayyor yeçim bor: functools.lru_cache. U hajm çegarasini qöllab-quvvatlaydi va statistikani ham körsatadi. maxsize=None — çeksiz xotira degani; Python 3.9 dan boşlab buning qisqa şakli functools.cache ham bor.
from functools import lru_cache
@lru_cache(maxsize=None)
def yollar_soni(qator: int, ustun: int) -> int:
"""Tör böylab faqat öngga va pastga yurib neça xil yöl bor."""
if qator == 0 or ustun == 0:
return 1
return yollar_soni(qator - 1, ustun) + yollar_soni(qator, ustun - 1)
print(yollar_soni(16, 16))
info = yollar_soni.cache_info()
print(f"Keşdan olindi: {info.hits}, hisoblandi: {info.misses}")
Natija:
601080390
Keşdan olindi: 225, hisoblandi: 288
Amaliy dekorator: jurnal (log) yoziş
Xatolarni qidirişda qaysi funksiya qanday argumentlar bilan çaqirilgani va nima qaytargani haqida maʼlumot juda foydali. Quyidagi dekorator buni avtomatik yozib boradi. Haqiqiy loyihalarda print örniga logging modulidan foydalaniladi, lekin ğoya bir xil.
from functools import wraps
def jurnal(funksiya):
@wraps(funksiya)
def orovchi(*args, **kwargs):
qismlar = [repr(a) for a in args]
qismlar += [f"{k}={v!r}" for k, v in kwargs.items()]
print(f"[LOG] {funksiya.__name__}({', '.join(qismlar)}) çaqirildi")
natija = funksiya(*args, **kwargs)
print(f"[LOG] {funksiya.__name__} -> {natija!r}")
return natija
return orovchi
@jurnal
def orta_baho(baholar: list[int], xona: int = 1) -> float:
return round(sum(baholar) / len(baholar), xona)
orta_baho([5, 4, 5, 3], xona=2)
Natija:
[LOG] orta_baho([5, 4, 5, 3], xona=2) çaqirildi
[LOG] orta_baho -> 4.25
Amaliy dekorator: ruxsatni tekşiriş
Veb-ilovalarda köp funksiyalar faqat maʼlum roldagi foydalanuvçilar uçun oçiq böladi. Tekşiruvni har bir funksiya içiga yoziş örniga, uni argumentli dekoratorga çiqaramiz. Funksiyaning birinçi argumenti foydalanuvçi deb kelişib olamiz.
from dataclasses import dataclass
from functools import wraps
@dataclass
class Foydalanuvchi:
ism: str
rol: str
def ruxsat(*rollar: str):
def dekorator(funksiya):
@wraps(funksiya)
def orovchi(foydalanuvchi: Foydalanuvchi, *args, **kwargs):
if foydalanuvchi.rol not in rollar:
raise PermissionError(f"{foydalanuvchi.ism} uçun ruxsat yöq")
return funksiya(foydalanuvchi, *args, **kwargs)
return orovchi
return dekorator
@ruxsat("admin", "öqituvçi")
def baho_qoy(kim: Foydalanuvchi, talaba: str, baho: int) -> str:
return f"{kim.ism}: {talaba}ga {baho} qöyildi"
ustoz = Foydalanuvchi("Malika opa", "öqituvçi")
talaba = Foydalanuvchi("Jasur", "talaba")
print(baho_qoy(ustoz, "Jasur", 5))
try:
baho_qoy(talaba, "Jasur", 5)
except PermissionError as xato:
print("Xato:", xato)
Natija:
Malika opa: Jasurga 5 qöyildi
Xato: Jasur uçun ruxsat yöq
Endi yangi “himoyalangan” funksiya kerak bölsa, tepasiga bitta @ruxsat(...) qatorini yoziş kifoya. Ruxsat qoidalari bitta joyda turadi va ularni özgartiriş oson.
Xulosa
- Python'da funksiyalar obyekt: ularni özgaruvçiga beriş, argument sifatida uzatiş va qaytariş mumkin.
- Içki funksiya taşqi özgaruvçilarni eslab qoladi (
closure); özgartiriş uçunnonlocalkerak. - Dekorator — funksiya olib, yangi funksiya qaytaradigan funksiya;
@nomyozuvif = nom(f)ning qisqa şakli. *args, **kwargsdekoratorni istalgan funksiyaga mos qiladi,functools.wrapsesa asl nom va hujjatni saqlaydi.- Argumentli dekorator uç qavatdan iborat: sozlama → dekorator → örovçi.
- Amaliy qöllanişlar: vaqtni ölçaş, keşlaş (
lru_cache), jurnal yoziş, ruxsat tekşiriş, qayta uriniş.
Maşqlar
necha_martadekoratorini yozing: u funksiya neça marta çaqirilganinichaqiruvlaratributida saqlasin.wrapsişlatişni unutmang.
Yeçim
from functools import wraps
def necha_marta(funksiya):
@wraps(funksiya)
def orovchi(*args, **kwargs):
orovchi.chaqiruvlar += 1
return funksiya(*args, **kwargs)
orovchi.chaqiruvlar = 0
return orovchi
@necha_marta
def salom(ism):
return f"Salom, {ism}"
salom("Ali")
salom("Vali")
print(salom.__name__, salom.chaqiruvlar)
takrorla(n)argumentli dekoratorini yozing: u funksiyaninmarta çaqirsin va barça natijalarni röyxat qilib qaytarsin.
Yeçim
import random
from functools import wraps
def takrorla(n: int):
def dekorator(funksiya):
@wraps(funksiya)
def orovchi(*args, **kwargs):
return [funksiya(*args, **kwargs) for _ in range(n)]
return orovchi
return dekorator
@takrorla(5)
def zar_tashla() -> int:
return random.randint(1, 6)
print(zar_tashla())
musbat_sondekoratorini yozing: funksiyaga berilgan pozitsion argumentlardan birortasi manfiy son bölsa,ValueErrorkötarsin. Uni ikki narxni qöşadigan funksiyada sinab köring.
Yeçim
from functools import wraps
def musbat_son(funksiya):
@wraps(funksiya)
def orovchi(*args, **kwargs):
for qiymat in args:
if isinstance(qiymat, (int, float)) and qiymat < 0:
raise ValueError(f"Manfiy qiymat: {qiymat}")
return funksiya(*args, **kwargs)
return orovchi
@musbat_son
def jami(a: int, b: int) -> int:
return a + b
print(jami(12_000, 5_500))
try:
jami(12_000, -5_500)
except ValueError as xato:
print("Xato:", xato)
- Rekursiv
faktorialfunksiyasini yozing va ungalru_cachehamda özingiz yozganvaqtni_olchadekoratorini qöllang. Qaysi tartibda yoziş kerakligini öylab köring: rekursiv çaqiruvlar har birida vaqt çiqarmasligi uçunvaqtni_olchani alohida örovçi funksiyaga qöying.
Yeçim
import time
from functools import lru_cache, wraps
def vaqtni_olcha(funksiya):
@wraps(funksiya)
def orovchi(*args, **kwargs):
boshlanish = time.perf_counter()
natija = funksiya(*args, **kwargs)
print(f"{funksiya.__name__}: {time.perf_counter() - boshlanish:.6f} s")
return natija
return orovchi
@lru_cache(maxsize=None)
def faktorial(n: int) -> int:
return 1 if n <= 1 else n * faktorial(n - 1)
@vaqtni_olcha
def faktorial_hisobla(n: int) -> int:
return faktorial(n)
print(len(str(faktorial_hisobla(200))), "xonali son")
print(len(str(faktorial_hisobla(200))), "xonali son (keşdan)")