Предпосылки

Для начала я должен убедиться, что все одинаково хорошо разбираются в теме. Если вам хорошо известны такие понятия как: переменные, параметры, возвращение значений переменной и функция в функции и вы не особо хотите слушать про это, вы можете промотать статью до параграфа «Декораторы» и начать оттуда.

Функции в роли переменных

Если вы не знали, вы можете присвоить переменной функцию, так же как вы бы присваивали обычные значения этой переменной.

>>> a = "soup"
>>> b = 4
>>> def how_much_food(food, quantity):
...     return "I've got {} {}s!".format(quantity, food)
>>> gerald = how_much_food
>>> gerald(a, b)
"I've got 4 soups!"

Функции в роли параметров и возвращаемые значения

Круто, правда? Теперь вы можете вызывать функцию в функции, так же как вы бы вызывали обычную переменную.

def call_this(f):
    f()
    print("Called it!")

def call_with_three(f):
    f(3)

def woof(times=1):
    print("Woof!" * times)

>>> call_this(woof)
"Woof!"
"Called it!"
>>> call_with_three(woof)
"Woof!Woof!Woof!"

Определение функции внутри другой функции

Также вы можете определить функцию внутри другой функции! Это может оказаться супер мощной штукой, что плавно подводит к одной из моих самых любимых головоломок, относительно программирования: попробуйте сделать так, чтобы данный тест прошел.

assert five(plus(three())) == 8

def three(operator=None):
    if operator is None:
        return 3
    else:
        return operator(3)

def five(operator):
    if operator is None:
        return 5
    else:
        return operator(5)

def plus(second_number):
    def inner(first_number):
        return first_number + second_number
    return inner

# Get it?
# five(plus(three()))
# five(plus(3)) -> def inner: return first_number + 3
# five(inner) -> inner(5) -> 5 + 3 -> 8

Как я говорил ранее, это лишь одна из головоломок, так что если у вас не получилось с первого раза, представьте, что вы решаете математическую задачу и попробуйте прописать программу на бумаге, делая замены. В любом случае, функции — это маленькие объекты, которые вы можете определить почти везде, где могли бы определить обычную переменную. Но будьте внимательны, функция не вызовется до тех пор, пока вы не поставите ( ) в конце. Что ж, мы плавно подобрались к основной теме данной статьи.

Декораторы

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

Плохой способ

Представим, что у нас есть функция, которая «лает» каждый раз, когда вызывается функция внутри неё. Почему лает? Потому что примеры очень тяжелые.

def pre_bark(func):
    def inner():
        print("Woof!")
        return func()
    return inner

def hello():
    print("Hello!")

Для того чтобы обернуть функцию «Привет» в «гавкающую» функцию, мы должны сделать вот так:

>>> hello = pre_bark(hello)
>>> hello()
"Woof!"
"Hello!"

Видите что происходит? Мы заменили «Привет» с внутренней функцией, которая печатает «Гав!». Но она делает это до вызова исходной функции. Это не круто. Не круто потому что мы должны заменить «Привет» после того как функция станет определенной. Вероятно кто-то взглянул на описание функции прежде чем вызвать её. Ничего не подразумевая, он ожидает увидеть «Привет», но внезапно получает в ответ «Гав!». Неожиданный сюрприз обычно самый лучший. Но что если наш код выглядел бы вот так:

Хороший способ

@pre_bark
def hello():
    print("Hello!")

При таком способе, вы сможете увидеть, что эта функция делает и, что она была немного изменена функцией pre_bark.

Что насчет переменных?

Для того чтобы перехватить значения переменных, которые были отправлены, внутренняя функция примет их в виде *args, **kwargs. Я не буду сейчас углублять в эту тему, но знайте, что вот тут есть очень хорошее объяснение.

def print_them_args(func):

    def the_name_of_this_one_doesnt_matter(*args):
        print("{} called with {}".format(func.__name__, [*args]))
        return func(*args)
    return the_name_of_this_one_doesnt_matter

@print_them_args
def add(a, b):
    return a + b

>>> add(5, 8)
add called with [5, 8]
13

Заключение первой части

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

Перевод статьи Ryan Palo: Unwrapping Decorators, Part 1