Python Decorators: Как да го използвам и защо?

Декораторът приема функция, добавя известна функционалност и я връща. В този урок ще научите как можете да създадете декоратор и защо трябва да го използвате.

Декоратори в Python

Python има интересна функция, наречена декоратори, за да добави функционалност към съществуващ код.

Това се нарича още метапрограмиране, защото част от програмата се опитва да модифицира друга част от програмата по време на компилация.

Предпоставки за обучение на декоратори

За да разберем за декораторите, първо трябва да знаем няколко основни неща в Python.

Трябва да се чувстваме комфортно с факта, че всичко в Python (Да! Дори класове) са обекти. Имената, които дефинираме, са просто идентификатори, обвързани с тези обекти. Функциите не са изключения, те също са обекти (с атрибути). Различни различни имена могат да бъдат обвързани с един и същ функционален обект.

Ето един пример.

 def first(msg): print(msg) first("Hello") second = first second("Hello")

Изход

 Здравей Здравей

Когато стартирате кода, двете функции firstи secondдават един и същ изход. Тук имената firstи се secondотнасят до същия обект на функция.

Сега нещата започват да стават по-странни.

Функциите могат да се предават като аргументи на друга функция.

Ако сте използвали функции като map, filterи reduceв Python, значи вече знаете за това.

Такива функции, които приемат други функции като аргументи, също се наричат функции от по-висок ред . Ето пример за такава функция.

 def inc(x): return x + 1 def dec(x): return x - 1 def operate(func, x): result = func(x) return result

Извикваме функцията, както следва.

 >>> operate(inc,3) 4 >>> operate(dec,3) 2

Освен това функция може да върне друга функция.

 def is_called(): def is_returned(): print("Hello") return is_returned new = is_called() # Outputs "Hello" new()

Изход

 Здравейте

Тук is_returned()е вложена функция, която се дефинира и връща всеки път, когато се обадим is_called().

И накрая, трябва да знаем за затварянията в Python.

Връщане към декоратори

Функциите и методите се наричат извикващи, както могат да бъдат извикани.

Всъщност всеки обект, който прилага специалния __call__()метод, се нарича извикваем. Така че, в най-основния смисъл, декораторът е извикващ, който връща извикващ.

По принцип декораторът приема функция, добавя известна функционалност и я връща.

 def make_pretty(func): def inner(): print("I got decorated") func() return inner def ordinary(): print("I am ordinary")

Когато стартирате следните кодове в черупката,

 >>> ordinary() I am ordinary >>> # let's decorate this ordinary function >>> pretty = make_pretty(ordinary) >>> pretty() I got decorated I am ordinary

В примера, показан по-горе, make_pretty()е декоратор. В стъпката за възлагане:

 pretty = make_pretty(ordinary)

Функцията ordinary()се декорира и върнатата функция получи името pretty.

Виждаме, че функцията декоратор добави нова функция към оригиналната функция. Това е подобно на опаковането на подарък. Декораторът действа като обвивка. Естеството на предмета, който е декориран (действителен подарък вътре) не се променя. Но сега изглежда доста (откакто е декориран).

Като цяло, ние украсяваме функция и я преназначаваме като,

 ordinary = make_pretty(ordinary).

Това е често срещана конструкция и поради тази причина Python има синтаксис, за да опрости това.

Можем да използваме @символа заедно с името на функцията декоратор и да го поставим над дефиницията на функцията, която ще бъде декорирана. Например,

 @make_pretty def ordinary(): print("I am ordinary")

е еквивалентно на

 def ordinary(): print("I am ordinary") ordinary = make_pretty(ordinary)

Това е просто синтактична захар за прилагане на декоратори.

Декориране на функции с параметри

Горният декоратор беше прост и работеше само с функции, които нямат никакви параметри. Ами ако имахме функции, които приемаха параметри като:

 def divide(a, b): return a/b

Тази функция има два параметъра, a и b. Знаем, че ще даде грешка, ако предадем b като 0.

 >>> divide(2,5) 0.4 >>> divide(2,0) Traceback (most recent call last):… ZeroDivisionError: division by zero

Сега нека направим декоратор, който да провери за този случай, който ще причини грешката.

 def smart_divide(func): def inner(a, b): print("I am going to divide", a, "and", b) if b == 0: print("Whoops! cannot divide") return return func(a, b) return inner @smart_divide def divide(a, b): print(a/b)

Това ново изпълнение ще се върне, Noneако възникне условието за грешка.

 >>> divide(2,5) I am going to divide 2 and 5 0.4 >>> divide(2,0) I am going to divide 2 and 0 Whoops! cannot divide

По този начин можем да декорираме функции, които вземат параметри.

Запален наблюдател ще забележи, че параметрите на вложената inner()функция в декоратора са същите като параметрите на функциите, които той декорира. Вземайки това предвид, сега можем да направим общи декоратори, които работят с произволен брой параметри.

In Python, this magic is done as function(*args, **kwargs). In this way, args will be the tuple of positional arguments and kwargs will be the dictionary of keyword arguments. An example of such a decorator will be:

 def works_for_all(func): def inner(*args, **kwargs): print("I can decorate any function") return func(*args, **kwargs) return inner

Chaining Decorators in Python

Multiple decorators can be chained in Python.

This is to say, a function can be decorated multiple times with different (or same) decorators. We simply place the decorators above the desired function.

 def star(func): def inner(*args, **kwargs): print("*" * 30) func(*args, **kwargs) print("*" * 30) return inner def percent(func): def inner(*args, **kwargs): print("%" * 30) func(*args, **kwargs) print("%" * 30) return inner @star @percent def printer(msg): print(msg) printer("Hello")

Output

 ****************************** %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Hello %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ******************************

The above syntax of,

 @star @percent def printer(msg): print(msg)

is equivalent to

 def printer(msg): print(msg) printer = star(percent(printer))

The order in which we chain decorators matter. If we had reversed the order as,

 @percent @star def printer(msg): print(msg)

The output would be:

 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ****************************** Hello ****************************** %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

Интересни статии...