Для чего нужен декоратор: Декораторы в 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(), тем самым добавляя в них новую функциональность, но не добавляя внутри функций ни строчки лишнего кода

Как это использовать и почему?

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

Декораторы в Python

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

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


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

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

Нам должно быть комфортно с тем фактом, что все в Python (Да! Даже классы) является объектами. Имена, которые мы определяем, являются просто идентификаторами, привязанными к этим объектам. Функции не являются исключением, они тоже объекты (с атрибутами). Различные имена могут быть связаны с одним и тем же функциональным объектом.

Вот пример.

 сначала по умолчанию (сообщение):
    печать (сообщение)
первый("Привет")
второй = первый
секунда ("Привет") 

Вывод

  Привет
Hello  

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

Теперь все становится еще более странным.

Функции могут передаваться в качестве аргументов другой функции.

Если вы использовали такие функции, как map , filter и reduce в Python, то вы уже знаете об этом.

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

 по умолч. вкл.(х):
    вернуть х + 1
деф дек(х):
    возврат х - 1
def работать (функция, х):
    результат = функция (х)
    возвращаемый результат 

Мы вызываем функцию следующим образом.

 >>> эксплуатация(вкл.3)
4
>>> работать(дек,3)
2 

Кроме того, функция может возвращать другую функцию.

 определение is_call():
    защита is_returned():
        распечатать("Привет")
    возврат is_returned
новый = is_вызванный ()
# Выводит "Привет"
новый() 

Вывод

  Hello  

Здесь is_returned() — это вложенная функция, которая определяется и возвращается каждый раз, когда мы вызываем is_call() .

Наконец, мы должны знать о замыканиях в Python.


Возвращаясь к декораторам

Функции и методы называются вызываемыми так, как их можно вызывать.

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

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

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

Когда вы запускаете следующие коды в оболочке,

 >>> обычный()
я обычный
>>> # украсим эту обычную функцию
>>> довольно = make_pretty(обычный)
>>> красивая ()
меня украсили
Я обычный 

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

 pretty = make_pretty(ordinary) 

Функция normal() была оформлена, а возвращаемой функции было присвоено имя красивая .

Мы видим, что функция декоратора добавила некоторые новые функции к исходной функции. Это похоже на упаковку подарка. Декоратор действует как обертка. Характер украшенного предмета (настоящего подарка внутри) не меняется. Но теперь он выглядит красиво (так как он был украшен).

Как правило, мы украшаем функцию и переназначаем ее как

 обычный = make_pretty(обычный). 

Это обычная конструкция, и по этой причине в Python есть синтаксис, упрощающий ее.

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

 @make_pretty
обычный ():
    print("Я обычный") 

эквивалентен

 def обычный():
    print("Я обычный")
обычный = make_pretty(ordinary) 

Это просто синтаксический сахар для реализации декораторов.


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

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

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

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

 >>> делить(2,5)
0,4
>>> разделить(2,0)
Traceback (последний последний вызов):
...
ZeroDivisionError: деление на ноль 

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

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

Эта новая реализация вернет None , если возникнет ошибка.

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

Таким образом, мы можем декорировать функции, принимающие параметры.

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

В Python эта магия выполняется как function(*args, **kwargs) . Таким образом, аргумента будет кортежем позиционных аргументов, а kwargs будет словарем аргументов ключевого слова. Примером такого декоратора будет:

 defworks_for_all(func):
    def внутренний (*args, **kwargs):
        print("Я могу украсить любую функцию")
        функция возврата (*args, **kwargs)
    return inner 

Объединение декораторов в цепочку в Python

Несколько декораторов могут быть объединены в цепочку в Python.

То есть функцию можно декорировать несколько раз разными (или одними и теми же) декораторами. Мы просто размещаем декораторы над нужной функцией.

 звездочка по умолчанию (функция):
    def внутренний (*args, **kwargs):
        печать("*" * 30)
        func(*args, **kwargs)
        печать("*" * 30)
    вернуться внутрь
Защитный процент (функция):
    def внутренний (*args, **kwargs):
        печать("%" * 30)
        func(*args, **kwargs)
        печать("%" * 30)
    вернуться внутрь
@звезда
@процент
Защитный принтер (сообщение):
    печать (сообщение)
принтер("Привет") 

Выход

  **********************************
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Привет
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
******************************  

Приведенный выше синтаксис

 @star
@процент
Защитный принтер (сообщение):
    print(msg) 

эквивалентно

 def принтер(msg):
    печать (сообщение)
принтер = звезда (процент (принтер)) 

Порядок, в котором мы цепляем декораторы, имеет значение. Если бы мы изменили порядок как

 @percent
@звезда
Защитный принтер (сообщение):
    печать (сообщение) 

Вывод будет:

  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
*********************************
Привет
*********************************
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%  

Декораторы в Python — GeeksforGeeks

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

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

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

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

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

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

Python3

DEF SHOUT (TEXT):

return . ))

 

орать = Shout

Печать (Yell ( 'Hello' ))

Выход:

Hell
HELLO

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

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

Python3

def shout(text):

     return text.upper()

 

def whisper(text):

Возврат Текст. Lelower ()

DEF ЗВИС0035 func( )

     print (greeting)

 

greet(shout)

greet(whisper)

Output:

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

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

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

Python3

 

def create_adder(x):

     def adder(y):

         return x + y

 

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

 

add_15 = create_adder( 15 )

 

print (add_15( 10 ))

Output:

 25 

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

Декораторы

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

Синтаксис для Decorator:  

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

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

Decorator can modify the behaviour :   

Python3

def hello_decorator(func):

 

    

    

      

    

    

     def inner1():

         print

40035 "Hello, this is before function execution" )

 

        

        

         func()

 

         print ( "This is после выполнения функции" )

          

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

0003

DEF FUNCTION_TO_BE_USUSE ():

Печать ( "Это внутри функции !!" )

. BER 9003 9003 9003 9003 9003 9003 9003 9003

.BER 9003 9003 .

3 9000.BE. function_to_be_used)

 

 

function_to_be_used()

7 Вывод перед выполнением этой функции,

3
Это внутри функции !!
Это после выполнения функции

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

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

Python3

import time

import math

 

def calculate_time(func):

      

    

    

    

     def inner1( * args, * * kwargs):

 

Начало = Время ()

func( * args, * * kwargs)

 

        

         end = time. time()

         Печать ( "Всего времени, взятого в:" , func .__ Название ______, конец - Начало)

Возврат

0034 inner1

 

 

 

@calculate_time

def factorial(num):

 

    

    

     time.sleep( 2 )

Печать (Математика. Факториал (num))

Фактический ( 10 )

Вывод:

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

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

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

Python3

def hello_decorator(func):

     def inner1( * args, * * kwargs):

          

         print ( "before Execution" )

возвращался_value = Func ( * Args, 4 * 9 * . 0035 * kwargs)

         print ( "after Execution" )

          

        

         return returned_value

          

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

 

 

@hello_decorator

def sum_two_numbers(a, b):

     print ( "Inside the function" )

     return a + b

A, B = 1 , 2

Печать ( "SUM =" , ( "SUM =" , ( ". 0003

Вывод:

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

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

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

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

Example:  

Python3

def decor1(func):

     def inner():

         x = func ()

         возврат x * x

     return inner

 

def decor(func):

     def inner():

         x = Func ()

Возврат 2 * x

return

4

4

4

.