Декоратор в Python — це шаблон проектування, який дозволяє модифікувати роботу функції, обернувши її в іншу функцію. Зовнішня функція називається декоратором, який приймає як аргумент вихідну функцію та повертає її модифіковану версію.
Що потрібно знати перед вивченням декораторів в Python?
Перш ніж ми поговоримо про декоратори, нам необхідно засвоїти кілька важливих понять, пов’язаних з функціями в Python. Також слід пам’ятати, що все в Python є об’єктом, навіть функції є об’єктами.
Вкладені функції
Ми можемо розмістити одну функцію всередині іншої — це називається вкладеною функцією. Наприклад:
|
1 2 3 4 5 6 7 8 |
def outer(x): def inner(y): return x + y return inner add_five = outer(5) result = add_five(6) print(result) |
Результат:
11
Тут ми створили функцію inner() всередині функції outer().
Передача функції як аргумент
В Python ми можемо передавати функцію як аргумент іншій функції. Наприклад:
|
1 2 3 4 5 6 7 8 |
def add(x, y): return x + y def calculate(func, x, y): return func(x, y) result = calculate(add, 4, 6) print(result) |
Результат:
10
Під час виклику функції calculate() ми передаємо функцію add() як аргумент. У функції calculate() аргументи: func, x і y перетворюються на add, 4 та 6 відповідно.
Повернення функції у вигляді значення
В Python ми також можемо повернути функцію у вигляді значення. Наприклад:
|
1 2 3 4 5 6 7 |
def greeting(name): def hello(): return "Hello, " + name + "!" return hello greet = greeting("Atlantis") print(greet()) |
Результат:
Hello, Atlantis!
Оператор return повертає внутрішню функцію hello(). Ця функція присвоюється змінній greet під час виклику зовнішньої функції greeting().
Декоратори в Python
Декоратор — це функція, яка приймає іншу функцію та повертає її модифіковану версію.
Примітка: Фактично, будь-який об’єкт, який реалізує спеціальний метод __call__(), називається викликаючим об’єктом. Таким чином, декоратор — це викликаючий об’єкт, який повертає викликаючий об’єкт.
Наприклад:
|
1 2 3 4 5 6 7 8 9 |
def make_pretty(func): def inner(): print("I got decorated") func() return inner def ordinary(): print("I am ordinary") |
Результат:
I am ordinary
Тут ми створили дві функції:
функція ordinary(), яка виводить I am ordinary;
функція make_pretty(), яка приймає як аргумент функцію, має вкладену функцію inner() та повертає її.
Ми викликаємо функцію ordinary() звичайним способом, тому отримуємо на виході I am ordinary. Тепер викличемо її за допомогою функції-декоратора:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
def make_pretty(func): # Визначаємо внутрішню функцію def inner(): # Додаємо функціонал print("I got decorated") # Викликаємо вихідну функцію func() # Повертаємо внутрішню функцію return inner # Визначаємо звичайну функцію def ordinary(): print("I am ordinary") # Створюємо функцію-декоратор decorated_func = make_pretty(ordinary) # Викликаємо функцію-декоратор decorated_func() |
Результат:
I got decorated
I am ordinary
Тут функція make_pretty() є декоратором. Зверніть увагу на наступний рядок коду:
|
1 |
decorated_func = make_pretty(ordinary) |
Ми передаємо функцію ordinary() як аргумент функції make_pretty(). Функція make_pretty() повертає внутрішню функцію inner(), яка присвоюється змінній decorated_func.
У наступному рядку коду ми фактично викликаємо функцію inner():
|
1 |
decorated_func() |
Символ @ з декоратором
Замість того, щоб присвоювати виклик функції змінній, Python пропонує більш елегантний спосіб зробити те саме за допомогою символу @. Наприклад:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
def make_pretty(func): def inner(): print("I got decorated") func() return inner @make_pretty def ordinary(): print("I am ordinary") ordinary() |
Результат:
I got decorated
I am ordinary
Тут функція ordinary() пов’язана з декоратором make_pretty() через використання синтаксису @make_pretty, що еквівалентно стейтменту ordinary = make_pretty(ordinary).
Декоратори та Функції з параметрами
Вищенаведений декоратор простий і працює тільки з функціями, що не мають параметрів. А якби у нас були функції, що приймають параметри, наприклад:
|
1 2 |
def divide(a, b): return a/b |
Ця функція має два параметри: a та b. Ми знаємо, що вона видасть помилку, якщо ми вкажемо в якості аргументу (b) значення 0.
Тепер зробимо декоратор для перевірки випадку, який може призвести до помилки (ділення на 0):
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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) divide(2,5) divide(2,0) |
Результат:
I am going to divide 2 and 5
0.4
I am going to divide 2 and 0
Whoops! cannot divide
Під час виклику divide(2,5) викликається функція inner(), визначена в декораторі smart_divide(). Оскільки другий аргумент не є 0, то функція inner() повертає управління у вихідну функцію divide(), передаючи аргументи 2 та 5, і функція divide() проводить обчислення та повертає результат 0.4.
Аналогічно, під час виклику функції divide() з аргументами 2 та 0 функція inner() перевіряє, що значенням параметра b є 0, і виводить повідомлення про помилку, після чого повертає None.
Ланцюжок декораторів в Python
В Python можна поєднувати кілька декораторів у ланцюжок. Для створення ланцюжка декораторів ми можемо застосувати декілька декораторів до однієї функції, розміщуючи їх один за одним.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
def star(func): def inner(*args, **kwargs): print("*" * 15) func(*args, **kwargs) print("*" * 15) return inner def percent(func): def inner(*args, **kwargs): print("%" * 15) func(*args, **kwargs) print("%" * 15) return inner @star @percent def printer(msg): print(msg) printer("Hello") |
Результат:
***************
%%%%%%%%%%%%%%%
Hello
%%%%%%%%%%%%%%%
***************
Наступна частина коду:
|
1 2 3 4 |
@star @percent def printer(msg): print(msg) |
рівнозначна
|
1 2 3 |
def printer(msg): print(msg) printer = star(percent(printer)) |
Порядок, у якому ми вибудовуємо ланцюжки декораторів, має значення. Якби ми змінили порядок на протилежний, наприклад:
|
1 2 3 4 |
@percent @star def printer(msg): print(msg) |
То результат був би:
%%%%%%%%%%%%%%%
***************
Hello
***************
%%%%%%%%%%%%%%%
