Функции-декораторы

Декоратор – приём программирования, который позволяет взять существующую функцию и изменить/расширить её поведение.

Декоратор получает функцию и возвращает обёртку, которая делает что-то своё «вокруг» вызова основной функции.

Простенький пример

Давайте самый простой пример декоратора, который пишет в вывод программы о том, что функция с каким-то именем была вызвана.

import logging
logging.basicConfig(level=logging.INFO)
def log(func):
def inner(*args, **kwargs):
logging.info(f"Function <{func.__name__}> is called")
return func(*args, **kwargs)
return inner

Если посмотреть определение выше, то вы заметите, что наш декоратор соответствует описанному.

Функция log получает оригинальную функцию и возвращает функцию-обертку(фейковую), которая в свою очередь вызывает оригинальную и реализовывает дополнительный функционал(в нашем случае вывод сообщения в лог)

note

Обратите внимание на то, что мы получаем оригинальное имя функции через обращение к аттрибуту __name__ объекта функции.

Как применять декоратор к функции

Без специального синтаксиса

Отлично, мы написали функцию-декоратор, но что с ним делать дальше? Давайте начнем с простого примера.

note

Должен напомнить, что функции в языке Python являются объектами первого класса - это значит, что:

  • функцию можно передать как аргумент при вызове другой фунции
  • можно вернуть объект функции как результат работы функции

Представим, что у нас нет специального синтаксиса декораторов, то как можно применить декорирующую функцию к другой функции?

def original():
pass
original = log(original)

Ну примерно так👆. Как видно, мы определили оригинальную функцию, вызвали декоратор log и переопределили значение переменной original.

Теперь, при вызове функции original мы будем видеть следующее сообщение в терминале:

original()
# Выведет в терминале:
'INFO:root:Function <original> is called'

С использованием символа @

Пример выше вполне себе рабочий, но чтобы сделать применение декораторов удобнее, мы можем воспользоваться специальным синтаксисом:

@log
def original():
pass

Так то удобнее!

Конфигурируемые декораторы

В дальнейшем вы может встретите, декораторы которые можно конфигурировать, но как это работает? В действительности вы можете написать функцию, которая в свою очередь будет возвращать функцию-декоратор. Примерно так:

def log(level: str):
def decorator(func):
def inner(*args, **kwargs):
getattr(logging, level)(f"Function <{func.__name__}> is called")
return func(*args, **kwargs)
return inner
return decorator
@log("info")
def foo():
pass

Применение декораторов к классам и методам классов

Применять декораторы можно не только к функциям, а так же к классам и методам классов.

TODO: Дописать примеры