Рубрики
Без рубрики

Демистификация декораторов питона менее чем за 10 минут

Обратите внимание: этот пост первоначально появился на Adrianperea.dev В этом быстром уроке мы узнаем … Tagged с Python, декораторы.

Обратите внимание: этот пост изначально появился на adrianperea.dev

В этом быстром уроке мы узнаем все о декораторах Python.

Декоратор Python – это Синтаксический сахар для написания Функциональные преобразования . Этот отрывок из PEP 318 описывает мотивацию для него:

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

Таким образом, понимание декораторов – это вопрос понимания, какие преобразования функций. Как только мы поймем их, мы легко переварить декораторов.

Итак, что являются Функциональные преобразования? Название звучит пугающе, но они довольно просты. По определению, Функциональные преобразования не более, чем функции, которые расширяют функциональность других функций, без изменения их исходного поведения. Они похожи на начинку на замороженном йогурте: они добавляют дополнительный зинг и ооомфа, но не отнимают ванильный вкус. Они сворачивать и Украсить наши функции, чтобы они придерживались определенного представления.

Они похожи на начинки на Froyo: они добавляют дополнительный Zing и Oomph, но не убирают ванильный вкус. Источник: Pixabay

Все это прояснится, как только мы начнем проходить несколько примеров. Давайте начнем.

Функции высшего порядка

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

Функции высшего порядка-это функции, которые имеют следующие свойства:

  • Они принимают функции в качестве аргументов

  • Они возвращают функции как результаты

Как можно применять функции высшего порядка к Python? Это просто: функции в Python являются первоклассными объектами. Это означает, что, как и любые другие объекты, функции могут:

  • быть назначенным переменным

  • передаваться в качестве функциональных аргументов

  • возвращаться из других функций

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

Посмотрим пример:

def say_hello(name):
    print(f'Hello, {name}!')


def say_goodbye(name):
    print(f'Goodbye, {name}!')


def say_to_bob(fun):
    fun('Bob')


say_to_bob(say_hello)
say_to_bob(say_goodbye)

Здесь, say_to_bob принимает функциональный параметр веселья. Затем мы можем использовать это, проходя скажи привет и say_goodbye в качестве аргументов для say_to_bob Анкет

Видя это в действии:

Hello, Bob!
Goodbye, Bob!

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

>>> say_to_bob(say_hello())
TypeError: say_hello() missing 1 required positional argument: 'name'

Это удовлетворяет первое условие для функции высшего порядка.

Теперь давайте изменим приведенный выше код, чтобы удовлетворить второе условие:

def say_hello(name):
    print(f'Hello, {name}!')


def say_goodbye(name):
    print(f'Goodbye, {name}!')


def get_greeting(greeting):
    if greeting == 'hello':
        greeting_fun = say_hello
    elif greeting == 'goodbye':
        greeting_fun = say_goodbye

    return greeting_fun


def say_to_bob(greeting):
    greeting_fun = get_greeting(greeting)
    greeting_fun('Bob')


say_to_bob('hello')
say_to_bob('goodbye')

get_greeting Функция возвращает другую функцию приветствия в зависимости от аргумента приветствия. say_to_bobcalls этой функции и получает ссылку на соответствующую функцию приветствия. Затем он вызывает эту функцию с Бобом.

Мы получаем тот же выход:

Hello, Bob!
Goodbye, Bob!

Обратите внимание еще раз, что когда мы возвращаем функции, мы опускаем скобки.

Функциональные преобразования вручную

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

Как я упоминал ранее:

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

Из этого определения давайте разбим преобразование функции на три свойства:

  • Это функция высшего порядка

  • Он добавляет дополнительную функциональность к функции передачи

  • Он сохраняет исходную функциональность прошедшей функции

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

def walkout():
    print('Bye Felicia')


def debug_transformer(fun):
    def wrapper():
        print(f'Function `{fun.__name__}` called')
        fun()
        print(f'Function `{fun.__name__}` finished')

    return wrapper


walkout = debug_transformer(walkout)
walkout()

Вы можете увидеть, что случилось? Давайте посмотрим на вывод:

Function `walkout` called
Bye Felicia
Function `walkout` finished

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

Мы сделали преобразование забастовка В этой линии:

walkout = debug_transformer(walkout)

Отсюда функция забастовка Больше не указывает на исходное определение функции. Теперь указывает на обертка функция, которая относится к нашей исходной функции:

def wrapper():
    print(f'Function `{fun.__name__}` called')
    fun() # Original reference to walkout()
    print(f'Function `{fun.__name__}` finished')

@-Syntax (читай: пирог-декоратор-синтаксис)

Источник: Pixabay

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

Чтобы упростить этот шаблон, команда Python представила @-Syntax. Посмотрим, как мы можем использовать его, чтобы упростить наш код выше:

def debug_transformer(fun):
    def wrapper():
        print(f'Function `{fun.__name__}` called')
        fun()
        print(f'Function `{fun.__name__}` finished')

    return wrapper


@debug_transformer
def walkout():
    print('Bye Felicia')

walkout()

Если вы запустите его, вы получите те же результаты, что и раньше:

Function `walkout` called
Bye Felicia
Function `walkout` finished

Намного лучше! За кулисами то, что делает @-Syntax, является следующей модификацией:

# Before
walkout = debug_transformer(walkout)

# After
@debug_transformer
def walkout():
    print('Bye Felicia')

@debug_transformer это упрощенная версия Callout = debug_transformer (забастовка) . Это облегчает чтение кода, так как определение декоратора и функции находится в том же месте. Аккуратный!

Исчезающие возвратные значения

Что произойдет, если мы применим наш декоратор на функцию с возвращаемой стоимостью?

def debug_transformer(fun):
    def wrapper():
        print(f'Function `{fun.__name__}` called')
        fun()
        print(f'Function `{fun.__name__}` finished')

    return wrapper


@debug_transformer
def walkout():
    print('Bye Felicia')


@debug_transformer
def get_bob():
    return 'Bob'

bob = get_bob()
print(bob)

Какие выводы:

Function `get_bob` called
Function `get_bob` finished
None

Украшение работает, но у нас больше нет ссылки на возвращаемое значение исходной функции. Чтобы сделать эту работу, измените обертку, чтобы вернуть результаты исходной функции:

def debug_transformer(fun):
    def wrapper():
        print(f'Function `{fun.__name__}` called')
        res = fun() # get reference to original return value
        print(f'Function `{fun.__name__}` finished')

        return res

    return wrapper

Теперь мы получаем наш ожидаемый результат:

>>> bob = get_bob()
Function `get_bob` called
Function `get_bob` finished
>>> print(bob)
Bob

Функции украшения с аргументами

Будет ли наш декоратор по -прежнему работать, если мы украсим функцию аргументом?

@debug_transformer
def walkout(name):
    print(f'Bye {name}')


walkout('Felicia')

Это дает нам следующую ошибку:

TypeError: wrapper() takes 0 positional arguments but 1 was given

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

Мы можем изменить обертку, чтобы получить произвольное количество аргументов с использованием *args и ** Kwargs Анкет При этом мы можем поддерживать функции с любой Количество аргументов:

def debug_transformer(fun):
    # Allow wrapper to receive arbitrary args
    def wrapper(*args, **kwargs):
        print(f'Function `{fun.__name__}` called')
        # And pass it to the original function
        res = fun(*args, **kwargs)
        print(f'Function `{fun.__name__}` finished')
        return res

    return wrapper

Попробуйте!

>>> walkout('Dionisia')
Function `walkout` called
Bye Dionisia
Function `walkout` finished

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

Еще пара примеров

Вы можете видеть, что в создании декораторов нет магии. Зная это, ваше творчество – единственный предел в их проектировании!

Вот несколько примеров для вдохновения:

Вызов функции много раз

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

def call_three_times(fun):
    def wrapper(*args, **kwargs):
        fun(*args, **kwargs)
        fun(*args, **kwargs)
        res = fun(*args, **kwargs)

        return res

    return wrapper


@call_three_times
def say_hey():
    print('Hey!')


say_hey()

# Output
Hey!
Hey!
Hey!

Время функции

Иногда мы хотим отметить, сколько времени требуется для работы. Мы можем сделать это без усилий с декораторами:

import time

def time_it(fun):
    def wrapper(*args, **kwargs):
        start = time.time()
        res = fun(*args, **kwargs)
        end = time.time()
        print(f'Function took {end-start}s')

        return res

    return wrapper


@time_it
def waste_time():
    for i in range(10000000):
        pass


waste_time()

# Output
Function took 0.18418407440185547s

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

Украшение внешних функций

Как мы можем применить декораторы на внешние функции, если мы не можем использовать @-Syntax для их украшения?

В начале статьи мы создали декораторы, вызывая функцию преобразуется непосредственно на функции. Мы можем сделать то же самое с внешними функциями! Скажем, мы хотим применить наш time_it декоратор для np.sort функция Мы можем сделать следующее:

import numpy as np

rng = np.random.RandomState(0)

# Create a lot of numbers
nums = rng.random(10000000)
# Decorate np.sort with our time_it transformer
timed_sort = time_it(np.sort)
# Perform the sort with our time_it functionality
timed_sort(nums)

Обратите внимание, что мы назначили украшенные np.sort к новому имени вместо переназначения его на np.sort Анкет Это позволяет сохранить исходные и преобразованные версии функции.

Вывод

В начале учебника мы узнали о:

  • Функции более высокого порядка : Функции, которые принимают и возвращают другие функции

  • Функциональные преобразования : метод использования функций высшего порядка для добавления функциональности

Затем мы говорили о том, как @-Syntax оптимизирует создание преобразования функций. Затем мы сделали декораторы, что:

  • Сохранить исходное возвращаемое значение функции

  • Принять произвольное количество аргументов

Мы закончили:

  • Видеть творческие примеры декораторов

  • Обучение, как украшать внешние функции

Просто, верно?

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

дальнейшее чтение

Привет! Я Адриан, и я инженер -программист. Я усердно работаю, чтобы бесплатно предоставить полезный и интуитивно понятный контент. Если вам нравится то, что вы читаете, проверьте мой блог или Следуйте Я в Твиттере. Надеюсь увидеть вас снова в следующий раз!

Оригинал: “https://dev.to/adrianmarkperea/demystifying-python-decorators-in-less-than-10-minutes-18c6”