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

Введение в декораторы Python

Мешают ли декораторы? В этом уроке вы получите более глубокое понимание декораторов Python.

Автор оригинала: Sheena.

Вступление

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

Пример использования: Выполнение функции Синхронизации

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

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

def func_a(stuff):
    do_important_things_1()
    do_important_things_2()
    do_important_things_3()

Один из способов продолжить-поместить наш код синхронизации вокруг каждого вызова функции. Итак, это:

func_a(current_stuff)

будет выглядеть немного больше так:

before = datetime.datetime.now()
func_a(current_stuff)
after = datetime.datetime.now()
print "Elapsed Time = {0}".format(after-before)

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

def func_a(stuff):
    before = datetime.datetime.now()
    do_important_things_1()
    do_important_things_2()
    do_important_things_3()
    after = datetime.datetime.now()
    print "Elapsed Time = {0}".format(after-before)

Преимущества такого подхода заключаются в следующем:

  1. У нас есть код в одном месте, поэтому, если мы хотим его изменить (например, если мы хотим сохранить прошедшее время в базе данных или журнале), нам нужно изменить его только в одном месте, а не при каждом вызове функции
  2. Нам не нужно помнить, что нужно писать четыре строки кода вместо одной каждый раз, когда мы вызываем func_a , что просто очень хорошо

Хорошо, но необходимость времени только для одной функции не так реалистична. Если вам нужно время для одной вещи, есть очень хороший шанс, что вам понадобится время по крайней мере для двух вещей. Так что мы пойдем на троих

def func_a(stuff):
    before = datetime.datetime.now()
    do_important_things_1()
    do_important_things_2()
    do_important_things_3()
    after = datetime.datetime.now()
    print "Elapsed Time = {0}".format(after-before)

def func_b(stuff):
    before = datetime.datetime.now()
    do_important_things_4()
    do_important_things_5()
    do_important_things_6()
    after = datetime.datetime.now()
    print "Elapsed Time = {0}".format(after-before)
    
def func_c(stuff):
    before = datetime.datetime.now()
    do_important_things_7()
    do_important_things_8()
    do_important_things_9()
    after = datetime.datetime.now()
    print "Elapsed Time = {0}".format(after-before)

Это выглядит довольно неприятно. Что делать, если мы хотим рассчитать время 8 функций. Затем мы решаем, что хотим сохранить информацию о времени в файле журнала. Затем мы решаем, что база данных будет лучше. Гадость-вот подходящее слово. Здесь нам нужен способ включить один и тот же код в func_a , func_b и func_c таким образом, чтобы мы не копировали вставленный код повсюду.

Краткий обход: Функции, возвращающие Функции

Python – довольно особый язык, в котором функции являются объектами первого класса . Это означает, что как только функция определена в области видимости, она может быть передана функциям, назначена переменным и даже возвращена из функций. Этот простой факт делает возможными декораторы python. Посмотрите на приведенный ниже код и посмотрите, можете ли вы догадаться, что происходит для строк с надписями A, B, C и D.

def get_function():
    print "inside get_function"                 
    def returned_function():                    
        print "inside returned_function"        
        return 1
    print "outside returned_function"
    return returned_function
   
returned_function()     # A                         
x = get_function()      # B                         
x                       # C                        
x()                     # D                     

A

Эта строка дает нам ошибку Name и утверждает, что returned_function не существует. Но мы только что определили это, верно? Здесь вам нужно знать, что он определен в области get_function. То есть внутри get_function он определен. Вне get_function это не так. если это сбивает вас с толку, попробуйте немного поиграть с функцией locals() и прочитать об области видимости Python.

B

При этом выводится следующее:

inside get_function
outside returned_function

На данный момент Python ничего не выполняет внутри returned_function .

C

Эта строка выводит:


То есть/| x , значение , возвращаемое из get_function () , само по себе является функцией.

Попробуйте снова запустить строки B и C. Обратите внимание, что каждый раз, когда вы повторяете этот процесс, адрес возвращаемой returned_function отличается. Каждый раз, когда вызывается get_function , он создает новую возвращаемую функцию .

D

Поскольку x является функцией, ее можно вызвать. Вызов x вызывает экземпляр returned_function . Что это такое:

inside returned_function
1

То есть он печатает строку и возвращает значение 1 .

Вернемся к проблеме выбора времени

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

def time_this(original_function):                            # 1
    def new_function(*args,**kwargs):                        # 2
        before = datetime.datetime.now()                     # 3
        x = original_function(*args,**kwargs)                # 4
        after = datetime.datetime.now()                      # 5
        print "Elapsed Time = {0}".format(after-before)      # 6
        return x                                             # 7
    return new_function()                                    # 8

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

1 Это всего лишь прототип time_this . time_this – это такая же функция, как и любая другая, и имеет один параметр. 2 Внутри time_this мы определяем функцию. Каждый раз, когда time_this выполняется, он создает новую функцию. 3 Код времени, как и раньше. 4 Мы вызываем исходную функцию и сохраняем результат на потом. 5,6 Остальная часть кода синхронизации. 7 Функция new_function должна действовать точно так же, как исходная функция, и поэтому возвращает сохраненный результат. 8 Функция, созданная в time_this , наконец, возвращается.

И теперь мы хотим убедиться, что наши функции синхронизированы:

def func_a(stuff):
    do_important_things_1()
    do_important_things_2()
    do_important_things_3()
func_a = time_this(func_a)        # <---------

def func_b(stuff):
    do_important_things_4()
    do_important_things_5()
    do_important_things_6()
func_b = time_this(func_b)        # <---------
    
def func_c(stuff):
    do_important_things_7()
    do_important_things_8()
    do_important_things_9()
func_c = time_this(func_c)        # <---------

Глядя на func_a , когда мы выполняем func_a(func_a) , мы заменяем func_a функцией, возвращаемой из time_this . Поэтому мы заменяем func_A функцией, которая выполняет некоторые временные функции (строка 3 выше), сохраняет результат func a в переменной с именем x (строка 4), выполняет немного больше временных функций (строки 5 и 6), а затем возвращает все, что func_a вернул бы в любом случае. Другими словами, func_a по-прежнему вызывается тем же способом и возвращает то же самое, он просто также получает время. Опрятно, а?

Знакомство с Декораторами

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

@time_this
def func_a(stuff):
    do_important_things_1()
    do_important_things_2()
    do_important_things_3()

В точности эквивалентно:

def func_a(stuff):
    do_important_things_1()
    do_important_things_2()
    do_important_things_3()
func_a = time_this(func_a)

Это обычно называют синтаксическим сахаром . В @ нет ничего волшебного . Это просто соглашение, которое было согласовано. Где-то в конце концов это было решено.

Вывод

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

  • Функции Python
  • Масштаб
  • Python функционирует как объекты первого класса (возможно, даже поиск лямбда-функций, это может облегчить понимание).

Если, с другой стороны, вы жаждете большего, чем темы, которые вы могли бы найти интересными, было бы:

  • Классы декорирования, например:

  • Декораторы с большим количеством аргументов например:

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