В программировании часто используются функции — мини-программы, которые делают внутри кода что-то своё. Это удобно, когда нужно несколько раз выполнить одно и то же: найти сумму квадратов, посчитать налог с зарплаты для каждого сотрудника или проверить логин и пароль пользователя.
Но бывает так, что иногда от функции нужно что-то ещё, а она этого не умеет. Чтобы умела и чтобы её не пришлось переписывать, используют декораторы. Сейчас объясним.
Как работают обычные функции
Функция в программировании — это код внутри основной программы, у которого есть своё внутреннее имя. Если использовать имя функции как команду, то программа выполнит этот мини-код, а потом продолжит работу дальше. Это делает код проще и чище, избавляет от повторов, ускоряет разработку, тестирование и совместную работу.
В 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 не был бы передан без дополнительных действий.
Ещё один недостаток декораторов: если к другой функции были прикреплены какие-то метаданные, они будут скрыты. Про то, что такое метаданные, — в другой раз.
Что дальше
А дальше мы попробуем на практике поработать с декораторами и сделаем с ними что-то полезное — например, замерим время работы программы или доработаем свою систему логирования.