В программировании часто используются функции — мини-программы, которые делают внутри кода что-то своё. Это удобно, когда нужно несколько раз выполнить одно и то же: найти сумму квадратов, посчитать налог с зарплаты для каждого сотрудника или проверить логин и пароль пользователя.
Но бывает так, что иногда от функции нужно что-то ещё, а она этого не умеет. Чтобы умела и чтобы её не пришлось переписывать, используют декораторы. Сейчас объясним.
Как работают обычные функции
Функция в программировании — это код внутри основной программы, у которого есть своё внутреннее имя. Если использовать имя функции как команду, то программа выполнит этот мини-код, а потом продолжит работу дальше. Это делает код проще и чище, избавляет от повторов, ускоряет разработку, тестирование и совместную работу.
В Python есть встроенные функции, например sum для суммирования и random для создания случайного числа. Если программисту нужны какие-то свои функции, он может их добавить. Достаточно описать функцию в коде один раз, а затем вызывать её, когда требуется. 
Чтобы сделать функцию в Python, нужно объявить её в коде служебным словом def, задать её имя и указать в скобках аргументы. Например, мы хотим, чтобы после авторизации программа приветствовала пользователя по имени, которое мы передаём в функцию. Для этого пишем такой код:
# функция, которая приветствует пользователя
def user_greeting(name): 
    # указываем, что делает функция
    print("Привет," + name)
В Python функции являются объектами. Это значит, что функцию можно передавать в другую функцию в качестве аргумента. Именно это свойство функций позволяет их декорировать.
Что такое декоратор
Если нам нужно, чтобы функция сделала что-то ещё, но мы не хотим переписывать её код, можно использовать декоратор. Это функция, которая расширяет возможности другой функции.
Декоратор можно сравнить с матрёшкой, которая содержит ещё одну матрёшку — другую функцию:

Например, у нас есть функция say_hi(), которая приветствует пользователя. Нам нужно, чтобы в нерабочее время пользователь получал предупреждение, что база недоступна из-за профилактических работ. 
В исходном виде функция say_hi() нам в этом не поможет — в ней не хватает нужных команд. Если мы будем добавлять их в исходную функцию, то программа может сломаться — дополнительные функции нужны только тут, а в других местах исходная функция и так работает хорошо. 
Декоратор как раз позволяет сделать так, чтобы приветствие пользователя в нерабочее время сменялось предупреждением.
Как сделать декоратор
Чтобы сделать декоратор для функции, нужно объявить его служебным словом def, задать имя и аргументы, а затем описать инструкцию нужных действий.
В простейшем виде декоратор выглядит так:
# объявляем функцию, которая будет служить декоратором
def my_decorator(func):
    # объявляем, что декоратор дополняет другую функцию какими-то действиями
    def wrapper():
        # указываем, что должно произойти до вызова другой функции
        print("Что-то выполняется до функции.")
        # указываем, что после этого должна работать другая функция
        func()
        # указываем, что должно произойти после того, как другая функция отработала
        print("Что-то выполняется после функции.")
    return wrapper
# объявляем функцию, которую декорируем
def say_hi():
    # указываем, что делает функция
    print("Привет!")
# указываем, что теперь функция декорирована
say_hi = my_decorator(say_hi)
Результат работы этого декоратора будет таким:

Попробуем изменить функцию say_hi(), чтобы она умела ещё и предупреждать о профилактических работах с базой.
Для этого нужно, чтобы какая-то одна новая функция проверяла время, а если оно нерабочее, то вторая новая функция выводила бы предупреждение.
# импортируем модуль даты и времени 
from datetime import datetime
# объявляем функцию, которая будет служить декоратором
def base_maintenance(func):
    # объявляем, что декоратор дополняет другую функцию какими-то действиями
    def wrapper():
        # проверяем, что время рабочее
        if 10 <= datetime.now().hour < 19:
            # если время рабочее, другая функция срабатывает
            func()
        else:
            # если время нерабочее, вместо другой функции появляется сообщение
            print("В нерабочее время база недоступна из-за профилактических работ.")
    return wrapper
# объявляем функцию, которую декорируем
def say_hi():
    # указываем, что делает функция
    print("Привет!")
# объявляем, что теперь функция декорирована
say_hi = base_maintenance(say_hi)
Логика такая:
- Сначала первая функция проверяет, сколько сейчас времени.
 - Если время больше 10 утра или меньше 7 вечера, срабатывает обычное приветствие пользователя.
 - Если время меньше 10 утра или больше 10 вечера, пользователь получает предупреждение, что база недоступна.
 
При запуске в нерабочее время получаем сообщение:

Получается, мы на основе старой функции получили новые возможности и нам не пришлось её переписывать.
Приведённый выше код получился немного топорным: в нём трижды указано название функции say_hi(). Чтобы сделать код проще, можно использовать символ @:
# вызываем модуль даты и времени 
from datetime import datetime
# объявляем функцию, которая будет служить декоратором
def base_maintenance(func):
    # объявляем, что декоратор дополняет другую функцию какими-то действиями
    def wrapper():
        # проверяем, что время рабочее
        if 10 <= datetime.now().hour < 19:
            # если время рабочее, другая функция срабатывает
            func()
        else:
            # если время нерабочее, вместо другой функции появляется сообщение
            print("В нерабочее время база недоступна из-за профилактических работ.")
    return wrapper
# используем декоратор
@base_maintenance
# определяем, какую функцию декорируем
def say_hi():
    print("Привет!")
Где применяются декораторы
В самых простых случаях декораторы можно использовать для таких операций:
- логирование — если нам нужно замерять время работы функции или программы;
 - кэширование — если при работе функции образуются промежуточные результаты и нам нужно запомнить их все;
 - ограничение скорости — если нам нужно замедлить работу какой-то функции;
 - повторное выполнение — если нам нужно, чтобы какая-то функция отработала два или больше раз;
 - контроль доступа — если нам нужно проверить, что пользователь авторизован.
 
Что может пойти не так
Фактически декоратор заменяет другую функцию, и его внутренняя функция wrapper не принимает никаких аргументов. Если бы мы декорировали не функцию say_hi(), а функцию user_greeting(name), то аргумент name не был бы передан без дополнительных действий.
Ещё один недостаток декораторов: если к другой функции были прикреплены какие-то метаданные, они будут скрыты. Про то, что такое метаданные, — в другой раз.
Что дальше
А дальше мы попробуем на практике поработать с декораторами и сделаем с ними что-то полезное — например, замерим время работы программы или доработаем свою систему логирования.