Python və "örtüklər"
-
Decorator-lar (örtük və ya bəzəyici kimi tərcümə oluna bilər) bir çox Python kitabxanası və çərçivələrində (framework) istifadə olunan özəllikdir.
Hər hansı metodu istifadə etməzdən qabaq görüləcək işləri özündə ehtiva edir. Məsələn, Django çərçivəsində hansısa spesifik bir keçiddə istifadəçinin öz hesabına daxil olub-olmadığını yoxlayırıq.
@login_required def account_view(request): return HttpResponse('Gizli səhifəyə xoş gəldiniz!')
account_view
funksiyasının üzərinə yazılan örtük (decorator) ora sorğu göndərməzdən qabaq, hesaba daxil olub olmadığını yoxayır. Əgər daxil olubsa cavab qaytarır, yoxsa giriş (login) səhifəsinə yönləndirir.
Bəs bunun işləmə prinsipi nədir? Necə olur ki, həmən funksiya çağırılmamışdan qabaqlogin_required
işə düşür?
Əslində işləmə prinsipi çox sadədir. İşin sehri sadəcə qısayol sintaksisindədir (syntactic sugar).İlk addımlar
Fərz edək ki, bir ədədi digərinə bölən funksiyamız var:
def divider(num1, num2): return num1/num2 divider(5, 2) divider(5, 0)
İkinci çağırılmada 5 və 0 ötürdük və 0-a bölmə olmadığından Python bizə ZeroDivisionError qaytardı.
Örtük (decorator) vasitəsi ilə biz çıxa biləcək xətaları əvvəlcədən idarə edə bilərik. İlk öncə sadə bir misala baxaq:def my_decorator(func): def wrapper(): print("Funksiyadan qabaq işləyəcək.") # parametr kimi ötürdüyümüz funksiya func() print("Funksiyadan sonra işləyəcək.") return wrapper def divide(): print("divide funksiyası") foo = my_decorator(divide) foo()
Bu kod bizə belə bir nəticə qaytaracaq:
Funksiyadan qabaq işləyəcək. divide funksiyası Funksiyadan sonra işləyəcək.
Burada biz
my_decorator
adında bir funksiya yartdıq. Hansı ki, içindəwrapper
adında daxili (inner) funksiya var. Əlavə olaraq, bir dədivide
funksiyası yaratdıq. Daha sonra foo adında bir dəyişən yaradıb,divide()
funksiyasını parametr kimi ötürdüyümüzmy_decorator
funksiyasına mənimsətdik.foo()
funksiyasını çağıranda ilk öncəmy_decorator
işə düşür, ardınca içindəkiwrapper()
. Ən sonda isədivide()
.
Göründüyü kimi çox sadə məntiqlə – bir funksiyanı digərinə ötürməklə, istənilən funksiyadan qabaq hər hansısa bir əməliyyat etmək mümkündürÖrtüklər və arqumentlər
Əgər bizim örtük ilə istifadə edəcəyimiz funksiyamız (divide) parametr qəbul edəcəksə, o zaman belə bir xəta ilə qarşılaşa bilərik:
def my_decorator(func): def wrapper(): print("Funksiyadan qabaq işləyəcək.") func() print("Funksiyadan sonra işləyəcək.") return wrapper def divide(num1, num2): print(num1/num2) foo = my_decorator(divide) foo(5, 2)
Traceback (most recent call last):
File “main.py”, line 12, in
foo(5, 2)
TypeError: wrapper() takes 0 positional arguments but 2 were givenBu xətanı biz ona görə aldıq ki, 4-cü sətirdə çağrılan
func()
metoduna heç bir arqument verməmişik. Halbuki,divide()
bizdən num1 və num2 dəyərlərini gözləyir.
Örtükdəki daxili funksiyamız olanwrapper()
əsas funksiyamızın (divider()) parametrlərini qəbul edə bilir. Biz də bunufunc()
-a ötürə bilərik:def my_decorator(func): def wrapper(num1, num2): print("Funksiyadan qabaq işləyəcək.") func(num1, num2) print("Funksiyadan sonra işləyəcək.") return wrapper def divide(num1, num2): print(num1/num2) foo = my_decorator(divide) foo(5, 2)
Bununla da xəta aradan qalxmış olur.
Əgər funksiya bizdəki kimi 2 yox daha çox parametr qəbul etsə hər dəfə bunları yazmaq o qədər də ağlabatan deyil. Üstəlik hər funksiya üçün eyni işi görən ayrı-ayrı örtüklər yazmalı olacağıq. Bunun həll yolu *args və **kwargs ikilisidir.
*args funksiyaya ötürülən istənilən mövqeli (positional) arqumentləri, **kwargs isə açar sözlü (keyword) arqumentləri qəbul edir.def my_decorator(func): def wrapper(*args, **kwargs): print("Funksiyadan qabaq işləyəcək.") func(*args, **kwargs) print("Funksiyadan sonra işləyəcək.") return wrapper def divide(num1, num2): print(num1/num2) foo = my_decorator(divide) foo(5, 2)
Qısayol sintaksisi (syntactic sugar)
Örtüyü
foo = my_decorator(divide)
kimi yazmaq əvəzinədef my_decorator(func): def wrapper(*args, **kwargs): print("Funksiyadan qabaq işləyəcək.") func(*args, **kwargs) print("Funksiyadan sonra işləyəcək.") return wrapper @my_decorator def divide(num1, num2): print(num1/num2) divide(5, 2)
kimi yaza bilərik.
Bir neçə örtük funksiyasını eyni bir funksiyanın üzərində istifadə etmək mümkündür:@my_decorator1 @my_decorator2 @my_decorator3 def divide(num1, num2): print(num1/num2)
Nəzərə almaq lazımdır ki, Python örtükləri aşağıdan yuxarıya doğru oxuyub, icra edəcək. Yəni birinci
@my_decorator3
icra olunacaq, ardınca@my_decorator2
və sonda@my_decorator3
.@wraps decorator
Python-da __name__, __doc__ və s. kimi hazır gələn (built-in) sehrli (magic) dəyişənlər var. Məsələn, __name__ bizə funksiyanın adını verir:
print(print.__name__)
“print” funksiyasının adını qaytarır.
Bu məntiqlə bizprint(divide.__name__)
kodunu icra etsək divide qaytarmalı idi, amma “wrapper” qaytardı. Çünki, bizdivide
-ımy_docator
-a mənimsətmişik. O da bizəwrapper()
metodunu qaytarır. Problemi həll etmək üçün Python-un@wraps
decorator-undan istifadə edəcəyik.from functools import wraps def my_decorator(func): @wraps(func) def wrapper(*args, **kwargs): print("Funksiyadan qabaq işləyəcək.") func(*args, **kwargs) print("Funksiyadan sonra işləyəcək.") return wrapper @my_decorator def divide(num1, num2): print(num1/num2) print(divide.__name__)
Daxili funksiya olan
wrapper()
funksiyasının üstünə@wraps
örtüyünü qoyub, parametr kimi dəfunc
dəyərini verdikdən sonradivide.__name__
bizə konsolda “divide” qaytaracaq.Və, sonda məsələnin həllinə gələk:
from functools import wraps def my_decorator(func): @wraps(func) def wrapper(num1, num2): if num2 == 0: print("You can not divide by zero.") else: func(num1, num2) return wrapper @my_decorator def divide(num1, num2): print(num1/num2) divide(5, 0) divide(5, 2)
Nəticə:
"You can not divide by zero." 2.5
Bilik paylaşdıqca artan bir sərvətdir