Asosiy kontentga o‘tish
Innovialabs

Python dekoratorlari: qanday ishlaydi va o‘zingiznikini yozish

Amaliyot3 daq

Qisqacha

Dekorator — funksiyani qabul qilib, chaqiruvdan oldin yoki keyin qo‘shimcha xatti-harakat qo‘shilgan yangi funksiyani qaytaruvchi funksiya; @decorator yozuvi shunchaki func = decorator(func) ning qisqa shakli.

Dekorator nima

Dekorator — boshqa funksiyani qabul qilib, yangi funksiya qaytaradigan funksiya. Funksiya ustiga @timer yozish — shunchaki func = timer(func) ning qisqa shakli. Dekoratorlar takrorlanadigan mantiqni — loglash, vaqtni o‘lchash, qayta urinish, ruxsatni tekshirish — biznes-koddan bitta joyga chiqarishga imkon beradi.

Ular Python’ning ikki xususiyati tufayli ishlaydi: funksiyalar oddiy obyektlar, ichki funksiya esa tashqi funksiyaning o‘zgaruvchilarini «eslab qoladi». Bu closure (yopilma) deyiladi.

1-qadam: closure

def make_greeter(greeting):
    def greet(name):
        return f"{greeting}, {name}!"
    return greet

hello = make_greeter("Salom")
print(hello("Anna"))  # Salom, Anna!

make_greeter tugaganidan keyin ham ichki greet funksiyasi greeting ni ko‘radi. Dekorator ham xuddi shunday ishlaydi, faqat tashqi funksiyaga satr emas, funksiya uzatiladi.

2-qadam: birinchi dekorator va functools.wraps

import functools
import time

def timer(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        print(f"{func.__name__}: {time.perf_counter() - start:.3f} s")
        return result
    return wrapper

@timer
def build_report(n):
    return sum(range(n))

Muhim jihatlar:

  • *args, **kwargs — o‘ram (wrapper) har qanday argumentni qabul qilib, ularni uzatadi.
  • return result — busiz dekoratsiyalangan funksiya None qaytaradi.
  • functools.wraps asl funksiyaning __name__, __doc__ va boshqa atributlarini ko‘chiradi. Usiz barcha funksiyalar wrapper deb nomlanadi, bu esa loglar, debugging va hujjatlarni buzadi.

3-qadam: argumentli dekorator

Dekoratorga parametrlar kerak bo‘lsa, yana bir daraja qo‘shiladi: tashqi funksiya sozlamalarni qabul qilib, dekoratorning o‘zini qaytaradi.

def retry(times=3, delay=1.0, exceptions=(Exception,)):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(1, times + 1):
                try:
                    return func(*args, **kwargs)
                except exceptions:
                    if attempt == times:
                        raise
                    time.sleep(delay)
        return wrapper
    return decorator

@retry(times=5, delay=0.5, exceptions=(ConnectionError,))
def fetch_rates():
    ...

E’tibor bering: qavsli @retry() va qavssiz @retry — turli narsalar. Ikkinchi holatda funksiya times parametriga tushadi va kod buziladi.

Amaliy misollar

Keshlash. Sof funksiyalar uchun o‘z keshingizni yozmang — standart kutubxonada functools.lru_cache va functools.cache bor:

@functools.lru_cache(maxsize=256)
def get_tariff(region_id):
    ...

Argumentlar hashlanadigan bo‘lishi kerak, kesh esa jarayon xotirasida saqlanadi.

Ruxsatni tekshirish. Birinchi argument sifatida foydalanuvchi keladigan handlerlarga dekorator qo‘yish qulay:

def require_role(role):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(user, *args, **kwargs):
            if role not in user.roles:
                raise PermissionError(f"{role} roli kerak")
            return func(user, *args, **kwargs)
        return wrapper
    return decorator

@require_role("admin")
def delete_order(user, order_id):
    ...

Klass-dekoratorlar va klasslar uchun dekoratorlar

Bular ikki xil tushuncha:

NimaKo‘rinishiQachon foydali
Dekorator sifatidagi klass__init__(self, func) va __call__ li klassholat saqlash kerak, masalan chaqiruvlar hisoblagichi
Klass uchun dekoratorfunksiya klassni qabul qilib, klass qaytaradiro‘yxatdan o‘tkazish, metod qo‘shish; @dataclass shunday ishlaydi
class CountCalls:
    def __init__(self, func):
        functools.update_wrapper(self, func)
        self.func = func
        self.calls = 0

    def __call__(self, *args, **kwargs):
        self.calls += 1
        return self.func(*args, **kwargs)

Ko‘p uchraydigan xatolar

  • functools.wraps ni unutish — nom va docstring yo‘qoladi.
  • O‘ramda return ni unutish — funksiya jimgina None qaytaradi.
  • Parametrli dekoratorlarda @deco va @deco() ni adashtirish.
  • Tartibni hisobga olmaslik: dekoratorlar pastdan yuqoriga qo‘llanadi, def ga eng yaqini birinchi.
  • async def ni oddiy o‘ram bilan o‘rash: korutinlar uchun o‘ram ham async bo‘lishi va await func(...) qilishi kerak.
  • Dekoratorga juda ko‘p mantiq yashirish — xatti-harakat nomidan tushunarli bo‘lmasa, kodni o‘qish qiyinlashadi.

Ko‘p beriladigan savollar

Har bir chaqiruv qo‘shimcha funksiya orqali o‘tadi, shuning uchun qo‘shimcha xarajat bor, lekin u odatda funksiyaning o‘z ishiga nisbatan juda kichik. Faqat juda «issiq» sikllarda sezilishi mumkin.

Bunda yordam beramiz

O‘zingiz shug‘ullanmasdan natija olishni istasangiz — biz har kuni shu bilan shug‘ullanamiz.

Loyihani muhokama qilish

Yana o‘qing

G‘oya yoki vazifangiz bormi? Bizga aytib bering

Ish kuni davomida javob beramiz va yechim taklif qilamiz. Konsultatsiya bepul.