Python dekoratorlar

Dekorator bu bir funksiyani olib uning asosiy logikasiga tegmasdan xususiyatlarini oshirib beradigan funksiya.

Dekorator nimaligini yaxshi tushinish uchun avval funksiya nimaligin yaxshi tushunib olish kerak. Funksiya berilgan argumentlarga asoslanib qiymat qaytaradi. Bunga eng sodda misol:


>>> def add_one(number):
...	return number + 1
>>> add_one(4)
5

Pythonda funksiyalar birinchi klass obyektlari hisoblanadi. Yani funksiyalar xuddi boshqa Python obyektlari (int, list, tuple, str va h.kz) kabi boshqa funksiyalar uchun argument sifatida berilishi, o'zgaruvshiga biriktirilishi mumkin.

Funksiyalar ichma ich yozilishi ham mumkin, shu qatorda bir funksiya o'zining ichidagi boshqa bir funksiyani chaqirishi ham mumkin.


def parent():
    print("Printing from the parent() function")

    def first_child():
        print("Printing from the first_child() function")

    def second_child():
        print("Printing from the second_child() function")

    second_child()
    first_child()

Parent funksiyani chaqirganda nima bo'lishiga nazar soling:


>>> parent()
Printing from the parent() function
Printing from the second_child() function
Printing from the first_child() function

Ko'rib turganingizdek parent ichidagi funksiyalarning yozilish ketma ketligi ahamiyatga ega emas, ularni chaqirilish ketma ketligi muhimdir.

Bir funksiya boshqa funksiyani qaytarishiga misol ko'ramiz.


def parent(num):
    def first_child():
        return "Hi, I am Emma"

    def second_child():
        return "Call me Liam"

    if num == 1:
        return first_child
    else:
        return second_child

Etibor bergan bo'lsangiz bu yerda funksiyalar oxirida qavs qo'yilmasdan qaytarilmoqda, bu degani biz chunchaki funksiyaga havola qaytarayapmiz. Parent funksiya qabul qilayotgan num argumentga asoslanib bir ikki ichki funksiyadan biriga havola olishimiz mumkin:


>>> first = parent(1)
>>> second = parent(2)

>>> first
function parent..first_child at 0x7f599f1e2e18>

>>> second
function parent..second_child at 0x7f599dad5268>

Eng sodda dekorator

Misol:


def my_decorator(func):
    def wrapper():
        print("Something is happening before the function is called.")
        func()
        print("Something is happening after the function is called.")
    return wrapper

def say_whee():
    print("Whee!")

say_whee = my_decorator(say_whee)

Nima deb o'ylaysiz say_wee() funksiyasini chaqirganda nima sodir bo'ladi ? Sinab ko'ramiz:


>>> say_whee()
Something is happening before the function is called.
Whee!
Something is happening after the function is called.

Etibor bergan bo'lsangiz bu yerda hechqanaqa yangilik yo'q, yuqorida hozirgina o'rgangan bilimlarimiz orqali sodda dekorator yasadik.

Shunday funksiyani dekoratorga olish ushbu qatorda sodir bo'layapti:


say_whee = my_decorator(say_whee)

say_wee() funksiya bu yerda my_decorator() funksiyasiga argument sifatida berilayapti, my_decorator() bo'lsa wrapper funksiyasini qavslarsi chaqirib qo'yayapti, yani shu orqali biz my_decorator()  ichidagi wrapper() funksiyasiga murojaat qilayapmiz, wrapper() funksiyasi esa bizning decoratorga argument sifatida berilgan funksiyamizni qaytarayapti:

>>> say_whee
function my_decorator..wrapper at 0x7f3c5dfd42f0>

Sodda qilib aytganda: Decorator bu funksiyaning asosiy xususiyatini o'zgartirmasdan uni o'rab turadigan boshqa bir funksiya ekan.

Davom etishdan oldin, yana bir misolga nazar solamiz. wrapper() funksiya ham odatiy Python funksiya bo'lganligi uchun uni xususiyati ham dinamik o'zgarishi mumkin. Qo'shnilarni bezovta qilmaslik uchun say_wee() funksiyamizni faqat kunduz kun ishlaydigan qilamiz:

from datetime import datetime

def not_during_the_night(func):
    def wrapper():
        if 7 <= datetime.now().hour < 22:
            func()
        else:
            pass  # Hush, the neighbors are asleep
    return wrapper

def say_whee():
    print("Whee!")

say_whee = not_during_the_night(say_whee)

Endi say_wee() funksiyasini kech soat 10 dan ertalab soat 7 gacha bo'lgan vaqtda chaqirsangiz u Wee! demaydi )):

>>> say_whee()
>>>

say_wee() funksiyasini dekoratorga olishda etirbor bergan bo'lsangiz birinchidan `say_wee` deb uch marta yozishga to'g'ri keldi, ikkinchidan bizga bir qaraganda yozgan kodimizda dekorator qatnashganligini anglab olish qiyin sababi odatiy ikkita Python funksiya yozdik. Ishimizni osonlashtirish va dekoratorni dekorator sifatida ajratib ko'rsatish uchun Pythonda @ ( odatda "pie sytax" deb nomlanadi) dan foydalanamiz. Ushbu misol yuqorida ko'rgan misolimiz bilan bir xil ishlaydi:

def my_decorator(func):
    def wrapper():
        print("Something is happening before the function is called.")
        func()
        print("Something is happening after the function is called.")
    return wrapper

@my_decorator
def say_whee():
    print("Whee!")

@my_decorator bu say_wee = my_decorator(say_wee) deb yozishning osonroq usuli.

Dekoratorlardan qayta foydalanish.

Dekoratorlar ham boshqa Python funksiyalari kabi oddiy funksiya, shunday ekan undan oson foydalanish uchun bizda barcha qurollar yetarli. Dekoratorlardan qayta foydalana olish uchun decorators.py fayl yarating va shu fayl ichida:

def do_twice(func):
    def wrapper_do_twice():
        func()
        func()
    return wrapper_do_twice

Endi boshqa bir faylda ushbu dekoratorni import qilib ishlatishimiz mumkin:

from decorators import do_twice

@do_twice
def say_whee():
    print("Whee!")

Ushbu misolni ishga tushirganda say_whee() ikki marta ishga tushganini guvohi bo'lishingiz mumkin:

>>> say_whee()
Whee!
Whee!

Funksiyalarga argument bergan holda dekoratorga olish.

Tasavvur qilaylik bizning funksiyamiz argumentlar qabul qiladi, biz uni dekoratorga ololamizmi ? Sinab ko'ramiz:

from decorators import do_twice

@do_twice
def greet(name):
    print(f"Hello {name}")

Afsuskiy bu kodni ishga tushirish xatolikka olib keldi:

>>> greet("World")
Traceback (most recent call last):
  File "", line 1, in 
TypeError: wrapper_do_twice() takes 0 positional arguments but 1 was given

Muammo shundaki dekoratorning wrapper_do_twice() funksiyasi argument olmaydi, demak ushbu funksiyaga bitta argument oladigan qilish yetarli. Ammo ushbu xolatda boshqa bir bir nechta argument oladigan, yoki umuman argument olaydigan funksiyalarni ushbu dekoratorga ololmay qolamiz. Shu sababli *args, **kwargs juftligidan foydalanamiz:

def do_twice(func):
    def wrapper_do_twice(*args, **kwargs):
        func(*args, **kwargs)
        func(*args, **kwargs)
    return wrapper_do_twice

Endi wrapper funksiya istalgancha argument ololadi, yoki umuman argument olmasligi ham mumkin, argument olgan taqdirda o'zi wrap qilayotgan funksiyaga argumentlarni yetkazib beradi. Shu orqali yuqorida dekoratorga olingan ikki funksiyamiz ham ishlaydi:

>>> say_whee()
Whee!
Whee!

>>> greet("World")
Hello World
Hello World

Dekoratorga olingan funksiyalardan qiymat olish.

Dekoratorga olingan funksiyalardan qaytayotgan qiymatlar qayerga ketadi ? Albatta buni dekorator hal qiladi. Ushbu sodda dekoratorga olinga funksiyani misol qilib ko'ramiz:

from decorators import do_twice

@do_twice
def return_greeting(name):
    print("Creating greeting")
    return f"Hi {name}"

Ishlatib ko'ramiz:

>>> hi_adam = return_greeting("Adam")
Creating greeting
Creating greeting
>>> print(hi_adam)
None

Afsuskiy None yani hechnarsa qaytmadi.

Sababi wrapper_do_twice hech narsa qaytarmayapti, u shunchaki o'zining berilgan funksiyani chaqirib qo'yayapti xolos. Buni to'g'rilash uchun biz wrapper_do_twice berilgan funksiyani qaytaradigan qilishimiz kerak:

def do_twice(func):
    def wrapper_do_twice(*args, **kwargs):
        func(*args, **kwargs)
        return func(*args, **kwargs)
    return wrapper_do_twice

Endi qaytariladigan qiymatda oxirgi return qilingan funksiyaning qiymati qaytadi:

>>> return_greeting("Adam")
Creating greeting
Creating greeting
'Hi Adam

Dekoratorga olingan funksiya aslida qaysi funksiya ?

Pythondagi eng katta qulayliklardan biri bu Python shellning mavjudligi va unda istalgan Python obyektni introspeksiya qila olishdir. Intorspeksiya bu obyektning o'zining barcha attributlari haqida va runtaymi haqida malumotlarini ko'rishdir. Misol uchun funksiya o'zining nomi va dokumentatsiyasini o'zida saqlaydi:

>>> print
<built-in function print>

>>> print.__name__
'print'

>>> help(print)
Help on built-in function print in module builtins:

print(...)
    <full help message>

Introspeksiya esa siz yozgan funksiyalar uchun ham ishlaydi:

>>> say_whee
<function do_twice.<locals>.wrapper_do_twice at 0x7f43700e52f0>

>>> say_whee.__name__
'wrapper_do_twice'

>>> help(say_whee)
Help on function wrapper_do_twice in module decorators:

wrapper_do_twice()

Etibor bersangiz say_wee funksiya dekoratorga olingandan keyin o'zining kimligini unutib qo'ygan ko'rinadi. U o'zini do_twice dekoratori ichidagi wrapper_do_twice funksiyasi deb o'ylayapti, mantiqan to'g'ri lekin biz uchun foydali malumot emas.

Bu xolatni to'g'rilash uchun wrapper_do_twice funksiyamizni ham @funktools.wraps dekoratoriga olamiz, bu bizga wrapper_do_twice ichidagi funksiyaning o'z malumotlarini o'zgarmasdan saqlanib qolishini ta'minlaydi.

import functools

def do_twice(func):
    @functools.wraps(func)
    def wrapper_do_twice(*args, **kwargs):
        func(*args, **kwargs)
        return func(*args, **kwargs)
    return wrapper_do_twice

say_wee funksiyani o'zgartirishning xojati yo'q.

>>> say_whee
<function say_whee at 0x7ff79a60f2f0>

>>> say_whee.__name__
'say_whee'

>>> help(say_whee)
Help on function say_whee in module whee:

say_whee()

:), dekoratorga olingandan keyin ham funksiya o'z malumotlarini saqlab qoldi!

Kelajakdagi kodlaringiz uchun dekorator skeletoni, osh bo'lsin:

import functools

def decorator(func):
    @functools.wraps(func)
    def wrapper_decorator(*args, **kwargs):
        # Do something before
        value = func(*args, **kwargs)
        # Do something after
        return value
    return wrapper_decorator