Изменение поведения функции при работе с декораторами в Python (часть 2)

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

Что такое декораторы?

Давайте для начала немного вспомним, что такое декораторы. Это один из наиболее недооцененных новичками инструментов, который при этом является одним из наиболее полезных. А все из-за того, что находятся за рамками привычного подхода к программированию. Они относятся к совсем другой области. 

Под декоратором подразумевается такая функция, дающая возможность обернуть другую. Что это означает? А то, что декоратор позволяет увеличить функциональность функции, при этом не изменяя ее кода. Именно это и делает декораторы такими популярными среди опытных разработчиков. Декоратор начинается с @.

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

Как работают декораторы

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

def decorator_function(func):

    def wrapper():

        print('Функция-обёртка!')

        print('Оборачиваемая функция: {}'.format(func))

        print('Выполняем обёрнутую функцию...')

        func()

        print('Выходим из обёртки')

    return wrapper

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

Внутри decorator_function() нами была определена другая специальная функция, обертка. 

А теперь давайте посмотрим на декоратор в действии.  

>>> @decorator_function

... def hello_world():

...     print('Hello world!')

...

>>> hello_world()

Оборачиваемая функция: <function hello_world at 0x032B26A8>

Выполняем обёрнутую функцию...

Hello world!

Выходим из обёртки

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

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

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

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

from datetime import datetime





def add_current_time(f):

    def wrapped(*args, **kwargs):

        return f(datetime.utcnow(), *args, **kwargs)

    return wrapped

А теперь приведем вариант, как можно использовать эту конструкцию. 

@add_current_time

def test(time, a, b):

    print('Я получил аргументы', a, b, 'at', time)

В результате, у нас появятся следующие значения. 

&gt;&gt;&gt; test(1, 2)




Я получил аргументы 1 2 at 2019-10-10 21:38:35.582887

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

Как использовать декоратор для изменения результата функции

А теперь давайте приведем одну задачу, которая встречается довольно часто. Нам надо изменить значение декорированной функции, которое возвращается.

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

Если говорить о фреймворке Flask, то это наглядный пример того, как может использоваться эта техника. Давайте рассмотрим такую функцию представления Flask

 @app.route('/')

def index():

    return jsonify({'hello': 'world'})

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

@app.route('/')

def index():

    return jsonify({'hello': 'world'})

Если же программой на Flask осуществляется имплементация API, то, с большой вероятностью, у вас будет несколько путей, оканчивающихся возвращением результата в формате JSON, который генерируется с использованием функции jsonify()

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

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

@app.route('/')

@to_json

def index():

    return {'hello': 'world'}

В этом примере функция index() возвращает словарь, который считается недействительным типом для ответа во Flask. Тем не менее, декоратор to_json выполняет оборачивание функции и возвращает ответ в формате JSON. 

Теперь мы приведем полный код программы, включающую, в том числе, и имплементацию декоратора. 

from flask import Flask, jsonify




app = Flask(__name__)




def to_json(f):

    def wrapped(*args, **kwargs):

        response = f(*args, **kwargs)

        if isinstance(response, (dict, list)):

            response = jsonify(response)

        return response

    return wrapped




@app.route('/')

@to_json

def index():

    return {'hello': 'world'}

В этом примере функция wrapped() лишь осуществляет вызов оригинальной функции. Здесь она называется f. После этого она проверяет, является ли значение, которое возвращается, списком либо словарем. Если оказывается, что это так, то тогда просто осуществляется вызов функции jsonify()

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

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

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

Да и если у вас есть старая версия Flask, то без него не обойтись. Ну и читать код, подобный описанному выше, также надо уметь.

Использование декораторов для проверки данных

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

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

Давайте приведем пример кода для этой техники. 

from flask import request, abort





ADMIN_TOKEN='fheje3$93m*fe!'




def only_admins(f):

    def wrapped(*args, **kwargs):

        token = request.headers.get('X-Auth-Token')

        if token != ADMIN_TOKEN:

            abort(401)  # не авторизован

        return f(*args, **kwargs)

    return wrapped





@app.route('/admin')

@only_admins

def admin_route():

    return "только администраторы могут получить доступ к этому маршруту!"

Здесь декоратор only_admins используется для того, чтобы найти HTTP-заголовок X-Auth-Token в запросе, что является входящим. После этого он осуществляет проверку его на предмет совпадения с токеном администратора, который является системным. Для простоты мы его сделали константой. Если заголовок токена отсутствует либо он есть, но не совпадает, тогда запускается функция abort() из Flask. Она здесь нужна для ответа 401 и для того, чтобы остановить последующие запросы. Иначе запрос может пройти, вызвав декорированную функцию при этом.

А теперь посмотрите на то, как в этом примере функции представления admin_route() применяются декораторы app.route и only_admins. Это называется цепью декораторов. 

Вообще, это очень сложная тема, которую нужно рассматривать отдельно. Тем не менее, вы должны понимать, что app.route является первым декоратором, включенным в эту цепь.

Выводы

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

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

Зачем мы это сделали? Это было сделано специально. Все дело в том, что создавать декораторы, которые, помимо тех, которые передаются в декорированную функцию, умеют принимать и собственные параметры, значительно сложнее. Поэтому данная тема должна быть рассмотрена отдельно. Поэтому необходимо быть готовым к тому, что будет еще сложнее изучать эту тему более глубоко. Но ведь программирование – это не только сложно, но и интересно, не так ли? Да еще и высоко оплачивается. Так что затраты времени и сил, которые вы потратите на обучение, обязательно окупятся.

ОфисГуру
Adblock
detector