Декоратори - це функції, які приймають іншу функцію як аргумент, щоб розширити або замінити поведінку переданої функції.
Якщо функція A є декоратором, а функція B - її аргументом, то функція A змінює, розширює або замінює поведінку функції B, не змінюючи код функції B.
Кажуть, що функція-декоратор A обгортає функцію B.
Хоча ми говоримо про «зміну» поведінки, обгорнута функція насправді не змінюється.
Поведінку або додають навколо обгорнутої функції (і того, що вона повертає), або поведінку обгорнутої функції підмінюють іншою поведінкою.
Функція вищого порядку - це функція, яка приймає одну або більше функцій як аргументи та/або повертає одну або більше функцій. Функція, використана як аргумент або повернута з іншої функції, є функцією першого класу. Функція Python як обʼєкт, який можна викликати, є функцією першого класу, яку можна зберегти у змінній або використати як аргумент, як і будь-яке інше значення чи обʼєкт. Функції вищого порядку та функції першого класу працюють разом, уможливлюючи декоратори.
Символ @ ставлять перед назвою функції-декоратора і розміщують безпосередньо над функцією, яку потрібно декорувати, ось так:
@decorator
def decorated_function():
pass
Деякі декоратори приймають аргументи:
@decorator_with_arg(name="Bob")
def decorated_function2():
pass
Якщо декоратор має визначені типові аргументи, у виклику @decorator() потрібно використати дужки, щоб декоратор працював, як і під час виклику будь-якої функції:
@decorator_with_default_arg()
def decorated_function3():
pass
Якщо декоратор приймає позиційні аргументи, їх неподання призведе до помилки, яка матиме приблизно такий вигляд:
TypeError: decorator_with_pos_arg() missing 1 required positional argument: 'name'
Синтаксис @decorator - це синтаксичний цукор або скорочення для виклику функції-декоратора і передавання їй декорованої функції як аргументу.
Нижче наведено приклади альтернативних способів виклику декоратора:
def function():
pass
function = decorator(function)
def function2():
pass
function2 = decorator_with_arg(name="Bob")(function2)
def function3():
pass
function3 = decorator_with_default_arg()(function3)
Більшість декораторів призначені розширювати або замінювати поведінку іншої функції, але деякі декоратори можуть не робити нічого, крім повернення функцій, які вони обгортають.
Декоратори - це функції, які приймають щонайменше один аргумент: функцію, яку вони обгортають. Зазвичай вони повертають або обгорнуту функцію, або результат виразу, який використовує обгорнуту функцію.
Простий декоратор, який лише повертає обгорнуту функцію, можна записати так:
>>> def do_nothing(func):
... return func
...
... @do_nothing
... def function4():
... return 4
...
>>> print(function4())
4
Декоратор може лише додавати побічні ефекти, як-от додаткову інформацію для журналювання:
>>> def my_logger(func):
... print(f"Entering {func.__name__}")
... return func
...
... @my_logger
... def my_func():
... print("Hello")
...
>>> my_func()
Entering my_func
Hello
Декоратор не повертає сам себе. Він може повертати свої аргументи-функції, іншу функцію або одне чи кілька значень, які замінюють повернене значення переданої чи декорованої функції. Якщо декоратор повертає іншу функцію, зазвичай він повертає внутрішню функцію.
Функцію можна визначити всередині функції. Таку вкладену функцію називають внутрішньою функцією. Декоратор може використати внутрішню функцію, щоб обгорнути свій аргумент-функцію. Тоді декоратор повертає свою внутрішню функцію. Внутрішня функція, своєю чергою, може повернути початковий аргумент-функцію.
Нижче наведено приклад декоратора, який використовують для перевірки:
def my_validator(func):
def my_wrapper(world):
print(f"Entering {func.__name__} with {world} argument")
if world == "Pluto":
print("Pluto is not a planet!")
else:
return func(world)
return my_wrapper
@my_validator
def my_func(planet):
print(f"Hello, {planet}!")
my_func("World")
#-> Entering my_func with World argument
#-> Hello, World!
my_func("Pluto")
#-> Entering my_func with Pluto argument
#-> Pluto is not a planet!
У першому рядку маємо визначення аргументу func декоратора.
У наступному рядку міститься визначення внутрішньої функції декоратора, яка обгортає аргумент func.
Оскільки внутрішня функція обгортає аргумент func декоратора, їй передають той самий аргумент, який передають func.
Зауважмо, що обгортка не зобовʼязана використовувати ту саму назву для аргументу, який було визначено в func.
Початкова функція використовує planet, а декоратор - world, і декоратор усе одно працює.
Внутрішня функція або повертає func, або, якщо world == "Pluto", виводить, що Плутон не планета.
Замість цього її можна написати так, щоб вона спричиняла ValueError.
Отже, внутрішня функція обгортає func і повертає або func, або робить щось, що замінює те, що зробила б func.
Декоратор повертає свою внутрішню функцію.
Внутрішня функція може повертати або не повертати початкову, передану функцію.
Залежно від того, який код умовно виконується у функції-обгортці чи внутрішній функції, func може бути повернуто, може бути спричинено помилку, або може бути повернуто значення типу, який повертає func.
Декоратори можна писати для функцій, що приймають довільну кількість аргументів, використовуючи синтаксис *args і **kwargs.
Нижче наведено приклад декоратора для функції, що приймає довільну кількість аргументів:
def double(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs) * 2
return wrapper
@double
def add(*args):
return sum(args)
print(add(2, 3, 4))
#-> 18
print(add(2, 3, 4, 5, 6))
#-> 40
Це працює для подвоєння поверненого значення функції-аргументу. Якщо ми хочемо потроїти, почетверити тощо повернене значення, ми можемо додати параметр до самого декоратора, як показано в наступному розділі.
Нижче наведено приклад декоратора, який можна налаштувати так, щоб він множив повернене значення декорованої функції на довільне число:
def multi(factor=1):
if factor == 0:
raise ValueError("factor must not be 0")
def outer_wrapper(func):
def inner_wrapper(*args, **kwargs):
return func(*args, **kwargs) * factor
return inner_wrapper
return outer_wrapper
@multi(factor=3)
def add(*args):
return sum(args)
print(add(2, 3, 4))
#-> 27
print(add(2, 3, 4, 5, 6))
#-> 60
Перші рядки перевіряють, що factor не дорівнює 0.
Потім визначають зовнішню обгортку.
Вона має ту саму сигнатуру, яку ми очікуємо для декоратора без параметрів.
Зовнішня обгортка має внутрішню функцію з тією самою сигнатурою, що й початкова функція.
Внутрішня обгортка виконує роботу з множення поверненого значення початкової функції на аргумент, переданий декоратору.
Зовнішня обгортка повертає внутрішню обгортку, а декоратор повертає зовнішню обгортку.
Нижче наведено приклад параметризованого декоратора, який керує тим, чи перевіряє він аргумент, переданий початковій функції:
def check_for_pluto(check=True):
def my_validator(func):
def my_wrapper(world):
print(f"Entering {func.__name__} with {world} argument")
if check and world == "Pluto":
print("Pluto is not a planet!")
else:
return func(world)
return my_wrapper
return my_validator
@check_for_pluto(check=False)
def my_func(planet):
print(f"Hello, {planet}!")
my_func("World")
#-> Entering my_func with World argument
#-> Hello, World!
my_func("Pluto")
#-> Entering my_func with Pluto argument
#-> Hello, Pluto!
Це дає змогу легко перемикатися між перевіркою на Pluto і її відсутністю, і робиться це без потреби змінювати my_func.