Декораторы в Python: прокачиваем функции
medium

Декораторы в Python: прокачиваем функции

Красиво и мощно

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

Но бывает так, что иногда от функции нужно что-то ещё, а она этого не умеет. Чтобы умела и чтобы её не пришлось переписывать, используют декораторы. Сейчас объясним.

Как работают обычные функции

Функция в программировании — это код внутри основной программы, у которого есть своё внутреннее имя. Если использовать имя функции как команду, то программа выполнит этот мини-код, а потом продолжит работу дальше. Это делает код проще и чище, избавляет от повторов, ускоряет разработку, тестирование и совместную работу.

В 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)

Логика такая:

  1. Сначала первая функция проверяет, сколько сейчас времени.
  2. Если время больше 10 утра или меньше 7 вечера, срабатывает обычное приветствие пользователя.
  3. Если время меньше 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 не был бы передан без дополнительных действий.

Ещё один недостаток декораторов: если к другой функции были прикреплены какие-то метаданные, они будут скрыты. Про то, что такое метаданные, — в другой раз.

Что дальше

А дальше мы попробуем на практике поработать с декораторами и сделаем с ними что-то полезное — например, замерим время работы программы или доработаем свою систему логирования.

Текст:

Христина Каранская

Главный редактор:

Михаил Полянин

Шеф-редактор:

Инна Долога

Художник:

Алексей Сухов

Корректор:

Ирина Михеева

Вёрстка:

Кирилл Климентьев

Соцсети:

Юлия Зубарева

Получите ИТ-профессию
В «Яндекс Практикуме» можно стать разработчиком, тестировщиком, аналитиком и менеджером цифровых продуктов. Первая часть обучения всегда бесплатная, чтобы попробовать и найти то, что вам по душе. Дальше — программы трудоустройства.
Вам может быть интересно
medium
[anycomment]
Exit mobile version