Decorators
Decorator - Декоратор⚑
Що таке декоратори. Навіщо вони потрібні⚑
Summary
Декоратор - структурний шаблон проєктування, який дозволяє модифікувати функцію або клас без зміни їхнього вихідного коду. У Python декоратор - це функція, яка приймає іншу функцію або клас як аргумент і повертає модифіковану версію цієї функції або класу.
З допомогою декоратора ми можемо додати код до або після виконання функції. Синтаксис декораторів базується на використанні символу @
, за яким слідує назва декоратора і перед функцією або класом, який потрібно декорувати.
Основна ідея декораторів полягає в тому, що вони додають додаткову функціональність до існуючої функції або класу, не змінюючи їхнього вихідного коду. Це зроблено шляхом обгортання (wrapping) оригінальної функції або класу в нову функцію або клас, яка виконує додаткову логіку.
Основне використання декораторів включає реалізацію логування, мемоізації, перехоплення виключень, аутентифікації та багато іншого. Завдяки декораторам можна використовувати ці функціональні можливості зокрема для багатьох функцій чи класів, спрощуючи процес розробки та підтримки програмного коду.
Декоратор використовує механізм замикання.
Приклад декоратора в Python, який друкує повідомлення перед виконанням функції.
def decorator(func):
def wrapper(*args, **kwargs):
print("Some logic before original func")
return func(*args, **kwargs)
return wrapper
@decorator
def my_function():
print("Original function")
print(my_function.__name__) # wrapper # NOTE: because we didn't use wraps
print(my_function.__closure__[0]) # <cell at 0x10...: function object at 0x108e25170>
print(my_function.__closure__[0].cell_contents.__name__) # my_function
Links
- Понимаем декораторы в Python'e, шаг за шагом. Шаг 1
- Понимаем декораторы в Python'e, шаг за шагом. Шаг 2
Що може бути декоратором. До чого можна застосовувати декоратор⚑
Декоратором може бути будь-який callable об'єкт: функція, лямбда, клас, екземпляр класу. У випадку з останнім визначається метод __call__
.
Декоратор можна застосовувати до будь-якого об'єкта, але найчастіше до функцій, методів і класів. Декорування зустрічається настільки часто, що для нього існує окремий оператор @
.
Якби не існувало оператора декорування, ми записали би код вище так:
Що станеться, якщо декоратор не повертає нічого⚑
Якщо в тілі функції немає оператора return
, виклик верне значення None
. Проте результат декоратора замінює декорований об'єкт. У випадку якщо декоратор поверне None
, і функція, яку ми декоруємо, також стане None
. При спробі викликати її після декорування отримаємо помилку "NoneType is not callable".
Чим відрізняється @foobar
від @foobar()
⚑
Перший випадок - це звичайне декорування функцією foobar.
Другий випадок - декорування функцією, яку поверне виклик foobar. Інакше це називається параметризований декоратор або фабрика декораторів.
Що таке фабрика декораторів⚑
Це функція, яка повертає декоратор. Наприклад, вам потрібен декоратор для перевірки прав. Логіка перевірки однакова, але прав може бути багато. Щоб не копіювати код, можна використати фабрику декораторів.
from functools import wraps
def has_perm(perm):
def decorator(view):
@wraps(view)
def wrapper(request):
if perm in request.user.permissions:
return view(request)
else:
return HTTPRedirect('/login')
return wrapper
return decorator
@has_perm('view_user')
def users(request):
...
Навіщо потрібний wraps
⚑
wraps
- це декоратор зі стандартного модуля functools
, який призначає функції-обгортці ті ж самі атрибути __name__
, __module__
, __doc__
, що й у початкової функції, яку декорують. Це потрібно для того, щоб після декорування функція-обгортка в стек-трейсах виглядала як декорована функція.
from functools import wraps
>>> def my_decorator(func):
... @wraps(func)
... def wrapper(*args, **kwargs):
... print('Calling decorated function')
... return f(*args, **kwargs)
... return wrapper
...
>>> @my_decorator
... def example():
... """Docstring"""
... print('Called example function')
...
>>> example()
Calling decorated function
Called example function
>>> example.__name__
'example'
>>> example.__doc__
'Docstring'