Декораторы в Python за три минуты

Декораторы представляют собой удобный для восприятия человеком способ расширения возможностей функции, метода или класса извне. Использование декораторов особенно полезно при декорировании (т. е. расширении) похожих функций для выполнения ими одного и того же, но без добавления ненужных повторений в коде.

Вот простой пример того, как расширить функцию с помощью декоратора:

@guard_zero
def divide(x, y):
    return x / y

Декоратор @guard_zero расширяет функциональность метода divide тем, что не допускает деления на 0. Однако пока что guard_zero не существует, ведь он еще не реализован. Далее покажем, как именно это сделать.

Как создать декоратор в Python

Лучший способ продемонстрировать, каковы декораторы в действии,  —  создать и задействовать один из них. Реализуем наш декоратор @guard_zero.

Вначале у нас есть только этот метод, который делит два числа:

def divide(x, y):
    return x / y

Проблема с этим методом в том, что нет проверки, не равно ли 0 значение y. Очевидное решение здесь  —  задействовать простую проверку if. Но есть и альтернативное решение: декораторы.

Начнем с создания простой функции-декоратора guard_zero:

def guard_zero(operate):
    def inner(x, y):
        if y == 0:
            print("Cannot divide by 0.")
            return
        return operate(x, y)
    return inner

Декоратор в Python  —  это такая же обычная функция. Она принимает в качестве аргумента функцию operate. Затем расширяет функциональность operate, создавая внутреннюю функцию и добавляя туда расширенное поведение. После чего возвращает внутреннюю функцию inner, которая становится новой версией функции operate.

И вот декоратор guard_zero готов. Теперь расширим (т. е. декорируем) функцию divide:

divide = guard_zero(divide)

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

Есть и более распространенный синтаксис применения декоратора. Если в предыдущем синтаксисе функция заменялась новой своей версией, то здесь декоратор просто ставится перед определением функции:

@guard_zero
def divide(x, y):
    return x / y

Такой синтаксис более удобен для восприятия человеком, и суть его ясна: метод divide расширяется, чтобы исключить деление на 0.

Теперь пора протестировать метод divide различными входными данными и убедиться, что декоратор делает то, что должен:

print(divide(5, 0))
print(divide(5, 2))

Вывод:

Cannot divide by 0.
None
2.5

Обратите внимание на None в выводе. А все потому, что декоратор guard_zero возвращает None, когда значение y равно 0.

Вот и все! Теперь вы знаете, что такое декораторы и как их использовать. А вот и весь код:

def guard_zero(operate):
    def inner(x, y):
        if y == 0:
            print("Cannot divide by 0.")
            return
        return operate(x, y)
    return inner
@guard_zero
def divide(x, y):
    return x / y
    
print(divide(5, 0)) # выводит Cannot divide by 0 («На 0 делить нельзя»)

Напомним: метод декоратора guard_zero принимает в качестве аргумента функцию divide и создает ее расширенную версию.

Когда же используются декораторы?

«К чему такие сложности?»  —  скажете вы. Достаточно написать простую проверку if внутри функции divide, сэкономив при этом несколько строк кода.

Мощь декораторов становится очевидной, когда видишь, что они помогают избежать повторения кода.

Представьте, что у вас десять подобных друг другу методов. Необходимо убедиться, что второй параметр каждого из них не равен 0. В этой ситуации вы:

  • либо потратите время на написание десяти одинаковых проверок if для каждого метода отдельно,
  • либо создадите декоратор и напишете @zero-guard перед каждой функцией.

Последний подход будет более предпочтительным, ведь в этом случае нужно просто написать декоратор в одном месте и добавить везде @zero-guard.

Заключение

В Python декораторы используются для расширения функциональных возможностей функции, метода или класса извне.

Например, чтобы не допустить деления на 0, задействуется декоратор guard_zero. Просто добавляем его перед определением метода и таким образом используем этот декоратор в любом месте кода:

@guard_zero
def divide(x, y):
    return x / y

Но когда надо задействовать декораторы? Главным образом когда нужно избежать повторений при использовании похожих методов. Вместо того, чтобы вносить одно и то же изменение в несколько методов, куда как проще сделать одно изменение, а затем декорировать им все методы.

Спасибо за внимание! Надеюсь, статья была вам полезна.

Читайте также:

Читайте нас в Telegram, VK и Яндекс.Дзен


Перевод статьи Artturi Jalli: Understand Python Decorators in 3 Minutes

Предыдущая статьяЧто в голосе моем? - Код!
Следующая статьяИнтересные подробности об объектах JavaScript