Содержание
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 крик (текст):
|
Вывод:
ПРИВЕТ HELLO
В приведенном выше примере мы присвоили функцию Shout переменной. Это не вызовет функцию, вместо этого он берет объект функции, на который ссылается крик, и создает второе имя, указывающее на него, крик.
Пример 2: Передача функции в качестве аргумента
Python3
def Shout(text):
|
Вывод:
HI , Я СОЗДАН ФУНКЦИЕЙ, ПЕРЕДАВАЕМОЙ В КАЧЕСТВЕ АРГУМЕНТА.привет, меня создала функция, переданная в качестве аргумента.
В приведенном выше примере функция приветствия принимает в качестве параметра другую функцию (в данном случае крик и шепот). Затем функция, переданная в качестве аргумента, вызывается внутри функции приветствия.
Пример 3: Возврат функций из другой функции.
Python3
|
Выход:
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
импорт время
9003 9
900 39
|
Вывод:
3628800 Общее время, затраченное на: factorial 2.0061802864074707
Что, если функция что-то возвращает или функции передается аргумент?
Во всех приведенных выше примерах функции ничего не возвращали, поэтому проблемы не было, но возвращаемое значение может понадобиться.
Python3
def hello_decorator(func): 9003 9
|
Вывод:
перед выполнением Внутри функции после казни Sum = 3
В приведенном выше примере вы можете заметить большую разницу в параметрах внутренней функции. Внутренняя функция принимает аргумент как *args и **kwargs, что означает, что кортеж позиционных аргументов или словарь аргументов ключевого слова может быть передан любой длины. Это делает его общим декоратором, который может украшать функцию с любым количеством аргументов.
Цепочка декораторов
Проще говоря, цепочка декораторов означает украшение функции несколькими декораторами.
Пример:
Python3
def decor1(func):
|
Вывод:
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()
.