Для чего нужен декоратор: Декораторы в Python: понять и полюбить

python — ЗАЧЕМ нужны декораторы? (НЕ как они работают, а ЗАЧЕМ)

Зачем нужны декораторы?

Кто читал классическое объяснение про декораторы:

# Декоратор - это функция, ожидающая ДРУГУЮ функцию в качестве параметра
def my_shiny_new_decorator(a_function_to_decorate):
    # Внутри себя декоратор определяет функцию-"обёртку".
    # Она будет (что бы вы думали?..) обёрнута вокруг декорируемой,
    # получая возможность исполнять произвольный код до и после неё.
    def the_wrapper_around_the_original_function():
        # Поместим здесь код, который мы хотим запускать ДО вызова
        # оригинальной функции
        print "Я - код, который отработает до вызова функции"
        # ВЫЗОВЕМ саму декорируемую функцию
        a_function_to_decorate()
        # А здесь поместим код, который мы хотим запускать ПОСЛЕ вызова
        # оригинальной функции
        print "А я - код, срабатывающий после"
    # На данный момент функция "a_function_to_decorate" НЕ ВЫЗЫВАЛАСЬ НИ РАЗУ
    # Теперь, вернём функцию-обёртку, которая содержит в себе
    # декорируемую функцию, и код, который необходимо выполнить до и после. 
    # Всё просто!
    return the_wrapper_around_the_original_function
# Представим теперь, что у нас есть функция, которую мы не планируем больше трогать.
def a_stand_alone_function():
    print "Я простая одинокая функция, ты ведь не посмеешь меня изменять?.."
a_stand_alone_function()
# выведет: Я простая одинокая функция, ты ведь не посмеешь меня изменять?..
# Однако, чтобы изменить её поведение, мы можем декорировать её, то есть
# Просто передать декоратору, который обернет исходную функцию в любой код,
# который нам потребуется, и вернёт новую, готовую к использованию функцию:
a_stand_alone_function_decorated = my_shiny_new_decorator(a_stand_alone_function)
a_stand_alone_function_decorated()
#выведет:
# Я - код, который отработает до вызова функции
# Я простая одинокая функция, ты ведь не посмеешь меня изменять?..
# А я - код, срабатывающий после

Далее идет пассаж:

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

a_stand_alone_function = my_shiny_new_decorator(a_stand_alone_function)
a_stand_alone_function()
#выведет:
# Я - код, который отработает до вызова функции
# Я простая одинокая функция, ты ведь не посмеешь меня изменять?..
# А я - код, срабатывающий после

Т.е. после этого пассажа — мы теряем возможность вызвать функцию в первоначальном виде. Теперь она ВСЕГДА декорирована.

И вопрос, зачем тогда декоратор был нужен?

1) Почему бы (если мы все равно теряем первоначальную функцию) просто не переписать изначальную функцию? (просто дописав в начале и в конце функции необходимые нам куски кода). Вот так:

def a_stand_alone_function():
    print "Я - код, который отработает до вызова функции"
    print "Я простая одинокая функция, ты ведь не посмеешь меня изменять?.."
    print "А я - код, срабатывающий после"

или, если дополнительный код большой и должен лежать отдельно, то:

def a_stand_alone_function():
        pre_function()
        print "Я простая одинокая функция, ты ведь не посмеешь меня изменять?. ."
        post_function()

2) Зачем вся эта свистопляска, вместо того, чтобы сделать простое решение, как в 1 вопросе?

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

Буду очень благодарен за ответ с объяснениями и, может быть, ПОНЯТНЫМИ примерами из реальной практики.

python — Что такое декоратор? Почему именно функция внутри функции?

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

Предположим у вас есть код из 10 миллионов строк, и 100 однострочных функций которые вызываются в 1000 разных частях кода.
И вдруг вам понадобилось добавить в эти функции какой-то функционал ‘Что-то делаем ДО’ и ‘Что-то делаем после’, который занимает 5000 срок.

Что вы будете делать, искать эти 1000 строк кода вызова функций, и для каждого вызова прописывать ‘Что-то делаем ДО’ и ‘Что-то делаем после’ 1000 раз, добавляя дополнительные 5 миллионов строк кода?

Или в каждую из 100 функций добавлять эти дополнительные 5000 строк кода, превращая 100 строк кода функций в 500 тысяч + 100 строк?
А если потом вам надо будет отключить этот функционал для части функций, будете искать и удалять эти тысячи строк, сколько это займет времени, останется ли читабельным ваш код?

Или вы можете сделать декоратор, и вместо дополнительных 500 тысяч строк в вашем подходе, добавить всего 5 тысяч строк самого декоратора + 100 строк
для декорирования функций.
Более того, можно гибко управлять поведением декоратора, например через аргументы декоратора, либо вовсе отключить его, удалив лишь одну строчку кода @dec_arg() над функцией, либо вообще декорируя функцию по месту вызова, как ниже для func_2

DecEnable = True  # глобальная переменная, которая управляет поведением декоратора dec_arg, позволяет централизовано отключить дополнительный код всех декораторов, задекорированных без указания аргумента enable, т.е. как как @dec_arg()
def dec_arg(enable=DecEnable):
    def decor(func):  # тут 5000 строк дополнительного функционала, которые располагаются не внутри каждой из функций, а в одном месте, только здесь
        def wrap(*a, **k):
            if enable:
                print(func.__name__, f'Что-то делаем ДО {(a, k)}')
                try: return func(*a, **k)
                finally: print(func.__name__, f'Что-то делаем после {(a, k)}')
            else: return func(*a, **k)
        return wrap
    return decor
# ниже код из 10 миллионов строк, и 100 однострочных функций
@dec_arg(enable=False)  # поведение декоратора управляется из аргумента декоратора enable=False, в данном случае дополнительный код декоратора отключен
def func_1(a):
    print(a)
def func_2(a): # эта функция вообще не задекорирована
    print(a)
@dec_arg()  # () - без аргументов - поведение декоратора управляется глобальной переменной DecEnable, можно отключить дополнительный код всех декораторов, просто установив ее в False
def func_99(a):
    print(a)
@dec_arg()  # вместо 5000 дополнительных строк кода, мы добавили только одну, весь код находится в декораторе
def func_100(a):
    print(a)
# вызов функций в 1000 разных частях кода
func_1(1)  # вызов декорированной функции, но тут дополнительный код декоратора отключен в аргументе декорирования
dec_arg(enable=True)(func_2)(2)  # декорирование функции по месту ее вызова, независимо от состояния DecEnable, дополнительный код декоратора выполнится, т. к. enable=True
func_2(3)  # а тут вызов не задекорированной функции
func_99(4)  # вызов декорированной функции, поведение декоратора управляется глобальной переменной DecEnable
func_100(5)  # вызов декорированной функции, поведение декоратора управляется глобальной переменной DecEnable

Для этого и нужна функция обертка внутри декоратора, чтобы можно было декорировать множество функций одним и тем-же декоратором @dec_arg(), тем самым добавляя в них новую функциональность, но не добавляя внутри функций ни строчки лишнего кода

Decorators in Python — GeeksforGeeks

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

Объекты первого класса

В Python функции объекты первого класса , что означает, что функции в Python могут использоваться или передаваться в качестве аргументов.
Свойства функций первого класса:

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

Рассмотрим приведенные ниже примеры для лучшего понимания.

Пример 1: Обработка функций как объектов.

Python3

def крик (текст):

     возврат text.upper()

 

print (крик( 'Привет' ))

 

орать = орать

 

печать (крик( «Привет» ))

Вывод:

 ПРИВЕТ
HELLO 

В приведенном выше примере мы присвоили функцию Shout переменной. Это не вызовет функцию, вместо этого он берет объект функции, на который ссылается крик, и создает второе имя, указывающее на него, крик.

Пример 2: Передача функции в качестве аргумента

Python3

def Shout(text):

     return text. верхний()

 

def шепот (текст):

     возврат text.lower()

 

def приветствие(функция):

    

     приветствие = func( )

     печать (приветствие)

 

приветствие(sh out)

приветствие (шепотом)

Вывод:

 HI , Я СОЗДАН ФУНКЦИЕЙ, ПЕРЕДАВАЕМОЙ В КАЧЕСТВЕ АРГУМЕНТА. 
привет, меня создала функция, переданная в качестве аргумента. 

В приведенном выше примере функция приветствия принимает в качестве параметра другую функцию (в данном случае крик и шепот). Затем функция, переданная в качестве аргумента, вызывается внутри функции приветствия.

Пример 3: Возврат функций из другой функции.

Python3

 

def create_adder(x):

     90 040 def add(y):

         возврат x + y

 

     возврат сумматор

 

add_15 = create_adder( 15 )

 

print (add_15( 10 ))

Выход:

 25 

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

Декораторы

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

Синтаксис декоратора:  

 @gfg_decorator
определение hello_decorator():
    печать("Гфг")
'''Приведенный выше код эквивалентен -
определение hello_decorator():
    печать("Гфг")
    
hello_decorator = gfg_decorator(hello_decorator)''' 

В приведенном выше коде gfg_decorator является вызываемой функцией, которая добавит некоторый код поверх какой-либо другой вызываемой функции, функции hello_decorator, и вернет функцию-оболочку.

Decorator может изменять поведение func):

 

    

    

      

900 38     

    

     def inner1():

         печать ( "Здравствуйте, это перед выполнением функции" )

 

        

         9004 0

         func()

 

         печать ( "Это после выполнения функции" )

          

     return inner1

 

 

def function_to_be_used():

     print ( "Это внутри функции !!" )

 

 

function_to_be_used = hello_decorator (function_to_be_used)

 

 

function_to_be_used()

Вывод: 

 Здравствуйте, это перед выполнением функции
Это внутри функции !!
Это после выполнения функции 

Давайте посмотрим на поведение приведенного выше кода и на то, как он выполняется шаг за шагом при вызове «function_to_be_used».

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

Python3

импорт время

импорт математика

 

по умолчанию calculate_time(func):

      

    

    

     9004 0

     def inner1( * args, * * kwargs):

 

        

         начало = time.time()

          

         функция( * аргументы, * * kwargs)

 

9003 9         

         end = time. time()

         9004 0 print ( "Общее время: " , func.__name__, конец - начало)

 

900 39      возврат inner1

 

 

 

@calculate_time

def factorial(num):

 

    

    

     time.sleep( 9004 0 2 )

     print (math.factorial(num))

 

factorial( 900 39 10 )

Вывод:

 3628800
Общее время, затраченное на: factorial 2.0061802864074707 

Что, если функция что-то возвращает или функции передается аргумент?

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

Python3

def hello_decorator(func):

def inner1( * args, * * kwargs):

9003 9           

         печать ( "перед выполнением" )

          

        

         return_value = 9004 0 функция( * аргументы, * * kwargs)

         печать ( "после выполнения" )

          

        

         возврат 900 39 return_value

        

     возврат внутренний1

 

 

@hello_decorator

def sum_two_numbers(a, b):

     print ( "Внутри функции" )

     возврат а + б

 

а, б = 1 , 2

 

печать ( "Сумма =" , sum_two_numbers(a, b))

Вывод:

 перед выполнением
Внутри функции
после казни
Sum = 3 

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

Цепочка декораторов

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

Пример:  

Python3

def decor1(func):

     def inner():

         x = функция ()

         возврат x * x

     возврат внутренний

 

по умолчанию 90 040 декор(функция):

     def внутренний():

         x = func()

         возврат 2 * x 9 0005      возврат внутренний

 

@decor1

@decor

def num():

     возврат 10

 

@decor

@decor1

def num2():

     возврат 10

   9 0040  

печать (число())

печать (num2())

Вывод:

 400
200 

Приведенный выше пример подобен вызову функции как –

 decor1(decor(num))
decor(decor1(num2)) 

 

Декораторы Python (с примерами)

В этом уроке мы узнаем о декораторах Python с помощью примеров.

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

Внешняя функция называется декоратором, который принимает исходную функцию в качестве аргумента и возвращает ее модифицированную версию.


Предпосылки для изучения декораторов

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

Вложенная функция

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

 по умолчанию внешний(х):
    защита внутренняя (у):
        вернуть х + у
    вернуться внутрь
add_five = внешний (5)
результат = add_five (6)
print(result) # печатает 11
# Вывод: 11 

Здесь мы создали функцию inner() внутри функции external() .

Передача функции в качестве аргумента

Мы можем передать функцию в качестве аргумента другой функции в Python. Например,

 def add(x, y):
    вернуть х + у
def вычислить (функция, x, y):
    функция возврата (x, y)
результат = вычислить (сложить, 4, 6)
print(result) # печатает 10 

Выход

  10  

В приведенном выше примере функция calculate() принимает функцию в качестве аргумента. При вызове calculate() мы передаем функцию add() в качестве аргумента.

В функции calculate() аргументы: func , x , y становятся добавить , 4 и 6 900 40 соответственно.

Следовательно, func(x, y) становится add(4, 6) , что возвращает 10 .

Вернуть функцию как значение

В Python мы также можем вернуть функцию как возвращаемое значение. Например,

 приветствие по умолчанию (имя):
    привет ():
        вернуть "Здравствуйте, " + имя + "!"
    вернуть привет
приветствие = приветствие ("Атлантида")
print(greet()) # печатает "Привет, Атлантида!"
# Вывод: Привет, Атлантида! 

В приведенном выше примере оператор return hello возвращает внутреннюю привет() функция. Эта функция теперь назначена переменной приветствия .

Вот почему, когда мы вызываем greet() как функцию, мы получаем результат.


Декораторы Python

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

Фактически, любой объект, реализующий специальный метод __call__() , называется вызываемым. Таким образом, в самом общем смысле декоратор — это вызываемый объект, который возвращает вызываемый объект.

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

 по определению make_pretty(func):
    защита внутренняя():
        print("Меня наградили")
        функция()
    вернуться внутрь
обычный ():
    print("Я обычный")
# Вывод: Я обычный 

Здесь мы создали две функции:

  • обычная() , которая печатает "Я обычный"
  • make_pretty() , который принимает функцию в качестве аргумента и имеет вложенную функцию с именем inner() и возвращает внутреннюю функцию.

Мы вызываем функцию normal() в обычном режиме, поэтому получаем вывод «Я обычный» . Теперь давайте вызовем его с помощью функции декоратора.

 по определению make_pretty(func):
    # определяем внутреннюю функцию
    защита внутренняя():
        # добавляем дополнительное поведение к украшенной функции
        print("Меня наградили")
        # вызов исходной функции
        функция()
    # вернуть внутреннюю функцию
    вернуться внутрь
# определить обычную функцию
обычный ():
    print("Я обычный")
    
# украшаем обычную функцию
decor_func = make_pretty (обычный)
# вызов декорированной функции
decor_func() 

Выход

  я получил награду
Я обычный  

В приведенном выше примере make_pretty() является декоратором. Обратите внимание на код:

 decorated_func = make_pretty(ordinary) 
  • Теперь мы передаем функцию normal() в качестве аргумента функции make_pretty() .
  • Функция make_pretty() возвращает внутреннюю функцию, и теперь она назначена decoratty_func переменная.
 decor_func() 

Здесь мы на самом деле вызываем функцию inner() , где мы печатаем

@ Symbol With Decorator

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

 по умолчанию make_pretty(func):
    защита внутренняя():
        print("Меня наградили")
        функция()
    вернуться внутрь
@make_pretty
обычный ():
    print("Я обычный")
обычный() 

Выход

  я получил награду
Я обычный  

Здесь функция normal() украшена декоратором make_pretty() с использованием синтаксиса @make_pretty , что эквивалентно вызову normal = make_pretty(ordinary) .


Декорирование функций параметрами

Приведенный выше декоратор был простым и работал только с функциями, у которых не было параметров. Что, если бы у нас были функции, принимающие такие параметры, как:

 по умолч. разделить(а, б):
    return a/b 

Эта функция имеет два параметра: a и b . Мы знаем, что это выдаст ошибку, если мы передадим b как 0 .

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

 по определению smart_divide(func):
    определение внутреннее (а, б):
        print("Я буду делить", а, "и", б)
        если б == 0:
            print("Упс! Делить нельзя")
            возвращаться
        функция возврата (а, б)
    вернуться внутрь
@smart_divide
Деф разделить (а, б):
    печать (а/б)
разделить(2,5)
разделить(2,0) 

Выход

  Я собираюсь разделить 2 и 5
0,4
Я собираюсь разделить 2 и 0
Упс! нельзя разделить  

Здесь, когда мы вызываем функцию Divide() с аргументами (2,5) , вместо этого вызывается функция inner() , определенная в декораторе smart_divide() .