Как понять декораторы в Python

Python Язык программирования

Как понять декораторы в Python

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

Предварительные знания

Прежде всего, чтобы понять декораторов, мы должны сначала понять очень важную концепцию Python: «Функции являются членами первого класса». Чтобы еще раз перевести это предложение, функция — это переменная особого типа, которая может быть передана функции в качестве параметра или возвращена в качестве возвращаемого значения, как и другие переменные.


def abc():
    print("abc")

def abc1(func):
    func()

abc1(abc)

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

Еще один фрагмент кода


def abc1():
    def abc():
        print("abc")
    return abc
abc1()()

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

Что ж, давайте сделаем еще один вопрос для размышления и реализуем функциюadd,достигатьadd(m)(n)Эквивалентноm+nЭффект. Если мы поймем концепцию первоклассного члена раньше, мы сможем написать ее ясно.

def add(m):
    def temp(n):
        return m+n
    return temp
print(add(1)(2))

Ну, выход здесь равен 3 .

текст

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

Давайте посмотрим на требование

Теперь у нас есть функция


def range_loop(a,b):
    for i in range(a,b):
        temp_result=a+b
    return temp_result

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

Наверное, мы подумали об этом и написали этот код

import time
def range_loop(a,b):
    time_flag=time.time()
    for i in range(a,b):
        temp_result=a+b
    print(time.time()-time_flag)
    return temp_result

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

import time
def range_loop(a,b):
    time_flag=time.time()
    for i in range(a,b):
        temp_result=a+b
    print(time.time()-time_flag)
    return temp_result
def range_loop1(a,b):
    time_flag=time.time()
    for i in range(a,b):
        temp_result=a+b
    print(time.time()-time_flag)
    return temp_result
def range_loop2(a,b):
    time_flag=time.time()
    for i in range(a,b):
        temp_result=a+b
    print(time.time()-time_flag)
    return temp_result

Давайте на минутку задумаемся, хм, Ctrl+C, Ctrl+V. эмммм Хорошо, тебе не кажется, что этот код сейчас особенно грязный? Что мы делаем, чтобы сделать его чистым?

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

import time
def time_count(func,a,b):
    time_flag=time.time()
    temp_result=func(a,b)
    print(time.time()-time_flag)
    return temp_result
    
def range_loop(a,b):
    for i in range(a,b):
        temp_result=a+b
    return temp_result
def range_loop1(a,b):
    for i in range(a,b):
        temp_result=a+b
    return temp_result
def range_loop2(a,b):
    for i in range(a,b):
        temp_result=a+b
    return temp_result
time_count(range_loop,a,b)
time_count(range_loop1,a,b)
time_count(range_loop2,a,b)

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


import time
def time_count(func,*args,**kwargs):
    time_flag=time.time()
    temp_result=func(*args,**kwargs)
    print(time.time()-time_flag)
    return temp_result
    
def range_loop(a,b):
    for i in range(a,b):
        temp_result=a+b
    return temp_result
def range_loop1(a,b):
    for i in range(a,b):
        temp_result=a+b
    return temp_result
def range_loop2(a,b):
    for i in range(a,b):
        temp_result=a+b
    return temp_result
time_count(range_loop,a,b)
time_count(range_loop1,a,b)
time_count(range_loop2,a,b)

Ну, теперь это выглядит немного прилично, но давайте подумаем еще раз, этот код на самом деле меняет способ вызова нашей функции, например, мы запускаем напрямуюrange_loop(a,b)Тем не менее, нет способа получить время выполнения функции. Итак, что если мы не хотим менять способ вызова функции, но также хотим получить время выполнения функции?

Это легко, просто замените его


import time
def time_count(func):
    def wrap(*args,**kwargs):
        time_flag=time.time()
        temp_result=func(*args,**kwargs)
        print(time.time()-time_flag)
        return temp_result
    return wrap
    
def range_loop(a,b):
    for i in range(a,b):
        temp_result=a+b
    return temp_result
def range_loop1(a,b):
    for i in range(a,b):
        temp_result=a+b
    return temp_result
def range_loop2(a,b):
    for i in range(a,b):
        temp_result=a+b
    return temp_result
range_loop=time_count(range_loop)
range_loop1=time_count(range_loop1)
range_loop2=time_count(range_loop2)
range_loop(1,2)
range_loop1(1,2)
range_loop2(1,2)

Эммм, так удобнее? Ни исходный режим работы не изменяется, ни время работы функции не выводится.

но. . . Вам не кажется, что ручная замена слишком отвратительна? ? ? Мяу-мяу-мяу? ? ? Есть ли что-то еще, чтобы упростить? ?

Что ж, Python знает, что мы дети, любящие сахар, и предоставляет нам новый синтаксический сахар, который также сегодня является мужским номером один, декоратором Decorator.

Разговор о декораторе

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

Наш код выше можно записать так


import time
def time_count(func):
    def wrap(*args,**kwargs):
        time_flag=time.time()
        temp_result=func(*args,**kwargs)
        print(time.time()-time_flag)
        return temp_result
    return wrap
@time_count    
def range_loop(a,b):
    for i in range(a,b):
        temp_result=a+b
    return temp_result
@time_count
def range_loop1(a,b):
    for i in range(a,b):
        temp_result=a+b
    return temp_result
@time_count
def range_loop2(a,b):
    for i in range(a,b):
        temp_result=a+b
    return temp_result
range_loop(1,2)
range_loop1(1,2)
range_loop2(1,2)

Ух ты, когда это пишешь, ты вдруг это понял!まさか? ? ? да, на самом деле@Символ на самом деле является синтаксическим сахаром, который передает наш предыдущий ручной процесс замены в среду для выполнения. Хорошо, позвольте мне описать это человеческими словами.@Функция состоит в том, чтобы передать обернутую функцию как переменную декорированной функции/классу и заменить исходную функцию значением, возвращаемым декорированной функцией/классом.

@decorator
def abc():
    pass

Как упоминалось ранее, на самом деле имеет место специальный процесс замены.abc=decorator(abc)Что ж, давайте зададим несколько вопросов для практики, хорошо?


def decorator(func):
    return 1
@decorator
def abc():
    pass
abc()

Что происходит с этим кодом? Ответ: Будет выброшено исключение. Зачем? Ответ: поскольку замена произошла во время украшения,abc=decorator(abc), после заменыabc1 . Целые числа нельзя вызывать как функцию по умолчанию.


def time_count(func):
    def wrap(*args,**kwargs):
        time_flag=time.time()
        temp_result=func(*args,**kwargs)
        print(time.time()-time_flag)
        return temp_result
    return wrap

def decorator(func):
    def wrap(*args,**kwargs):
        temp_result=func(*args,**kwargs)
        return temp_result
    return wrap

def decorator1(func):
    def wrap(*args,**kwargs):
        temp_result=func(*args,**kwargs)
        return temp_result
    return wrap

@time_count
@decorator
@decorator1    
def range_loop(a,b):
    for i in range(a,b):
        temp_result=a+b
    return temp_result

Как заменить этот код? отвечать:time_count(decorator(decorator1(range_loop)))

Хм, теперь у вас есть общее представление о том, что такое декораторы?

расширяться

Теперь я хочу изменить то, что я написал ранееtime_countфункцию, пусть поддерживает передачу вflagпараметр, когдаflagзаTrueПри , время работы выходной функции равноFalseне выводит время, когда

Давайте пошагово, сначала предположим, что новая функция вызываетсяtime_count_plus

Эффект, которого мы хотим добиться, заключается в следующем.

@time_count_plus(flag=True)
def range_loop(a,b):
    for i in range(a,b):
        temp_result=a+b
    return temp_result

Ну мы посмотрели, сначала позвонилиtime_count_plus(flag=True)один раз замените значение, которое оно возвращает как декорированную функциюrange_loop, хорошо, тогда мы сначалаtime_count_plusЧтобы получить параметр, верните функцию, верно?

def time_count_plus(flag=True):
    def wrap1(func):
        pass
    return wrap1

Хорошо, теперь функция возвращается как декоратор, и мы говорим@На самом деле запускается процесс замены, так это наша текущая замена?range_loop=time_count_plus(flag=True)(range_loop)Что ж, теперь всем должно быть ясно, что мы находимся вwrap1Разве в нем все еще не должна быть функция и возврат?

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

def time_count_plus(flag=True):
    def wrap1(func):
        def wrap2(*args,**kwargs):
            if flag:
                time_flag=time.time()
                temp_result=func(*args,**kwargs)
                print(time.time()-time_flag)
            else:
                temp_result=func(*args,**kwargs)
            return temp_result
        return wrap2
    return wrap1
@time_count_plus(flag=True)
def range_loop(a,b):
    for i in range(a,b):
        temp_result=a+b
    return temp_result

Неужели так понятнее!

расширить в два раза

Ну, теперь у нас есть новые требования

m=3
n=2
def add(a,b):
    return a+b

def sub(a,b):
    return a-b

def mul(a,b):
    return a*b

def div(a,b):
    return a/b

Теперь у нас есть строкиa , aЗначение может быть+,-,*,/Итак, теперь мы хотимaКак вызвать соответствующую функцию со значением ?

Давай поджарим яичницу и подумаем, гм, логическое суждение


m=3
n=2
def add(a,b):
    return a+b

def sub(a,b):
    return a-b

def mul(a,b):
    return a*b

def div(a,b):
    return a/b
a=input('请输入 + - * / 中的任意一个\n')
if a=='+':
    print(add(m,n))
elif a=='-':
    print(sub(m-n))
elif a=='*':
    print(mul(m,n))
elif a=='/':
    print(div(m,n))

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

m=3
n=2
def add(a,b):
    return a+b

def sub(a,b):
    return a-b

def mul(a,b):
    return a*b

def div(a,b):
    return a/b
func_dict={"+":add,"-":sub,"*":mul,"/":div}
a=input('请输入 + - * / 中的任意一个\n')
func_dict[a](m,n)

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

m=3
n=2
func_dict={}
def register(operator):
    def wrap(func):
        func_dict[operator]=func
        return func
    return wrap
@register(operator="+")
def add(a,b):
    return a+b
@register(operator="-")
def sub(a,b):
    return a-b
@register(operator="*")
def mul(a,b):
    return a*b
@register(operator="/")
def div(a,b):
    return a/b

a=input('请输入 + - * / 中的任意一个\n')
func_dict[a](m,n)

Хорошо, помните, что мы говорили ранее об использовании@Когда используется грамматика, действительно ли она запускает процесс замены? Вот использование этой функции для регистрации карты функций при срабатывании декоратора, чтобы мы могли напрямую получать данные обработки функции в соответствии со значением «а». Также обратите внимание, что здесь нам не нужно изменять исходную функцию, поэтому нам не нужно писать функцию третьего уровня.

Если вы знакомы с Flask, то знаете, что при вызовеrouteЭта функция также используется, когда метод регистрирует маршрут.Можно обратиться к другой фигне гидрологии, написанной давным-давно.Новичок, читающий серию исходных кодов Flask (1): предварительное изучение маршрутизатора Flask

Суммировать

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

Что ж, давайте сегодня первыми напишем сюда!