Декоратори Python: як ним користуватися і чому?

Декоратор бере функцію, додає деяку функціональність і повертає її. У цьому підручнику ви дізнаєтеся, як можна створити декоратор і чому вам слід його використовувати.

Декоратори в 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)

This is just a syntactic sugar to implement decorators.

Decorating Functions with Parameters

The above decorator was simple and it only worked with functions that did not have any parameters. What if we had functions that took in parameters like:

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

This function has two parameters, a and b. We know it will give an error if we pass in b as 0.

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

Now let's make a decorator to check for this case that will cause the error.

 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)

This new implementation will return None if the error condition arises.

 >>> 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

In this manner, we can decorate functions that take parameters.

A keen observer will notice that parameters of the nested inner() function inside the decorator is the same as the parameters of functions it decorates. Taking this into account, now we can make general decorators that work with any number of parameters.

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 ****************************** %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

Цікаві статті...