Что такое декоратор? Почему именно функция внутри функции?
Огромная просьба, подскажите, пожалуйста, почему декоратор - это именно функция внутри функции? Почитал несколько статей, но нигде не вижу ответа конкретно на этот вопрос.
def func_decor2(some_func):
print('Что-то делаем ДО')
some_func()
print('Что-то делаем после')
Чем не декоратор?
Единственное, что приходит на ум по использованию дополнительной функции внутри - удобная передача аргументов, если наша some_func() будет требовать атрибуты.
Огромная просьба внятно и чётко объяснить, как именно устроен декоратор и почему именно так, когда внутри декоратора обязательно должна быть еще одна функция-"обёртка".
Заранее большое спасибо!
Ответы (3 шт):
По большому счету декоратор это способ избежать многократного дублирования кода.
Предположим у вас есть код из 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(), тем самым добавляя в них новую функциональность, но не добавляя внутри функций ни строчки лишнего кода
То что вы написали есть декоратор, который декорирует это конкретный вызов функции. Чтобы его использовать в коде, надо менять код: если вы где-то вызывали оригинальную функцию теперь надо сменить её имя на декорированную.
А если вы используете декоратор в стандартном смысле, вы меняете поведение всех вызовов функции везде где она упомянута по имени.
Пример:
def f():
print("I'm f!")
def g():
f()
f()
Чтобы декорировать f и воспользоваться новой версией надо написать:
def decorated(f):
print("before f")
f()
print("after f")
def g2():
decorated(f)
decorated(f)
g2()
Пришлось заменить g на g2. Если мы собирались использовать декоратор для отладки, в этом мало смысла, надо переписать весь отлаживаемый код.
Вот вариант с обычным декоратором:
def decorated(f):
def ff():
print("before f")
f()
print("after f")
return ff
f = decorated(f)
g()
Куда удобнее, g переписывать не надо, поведение f изменилось по всему коду, хотя сам код остался прежним.
В последнем варианте становится ясно почему нужно возвращать функцию - то что возвращает декоратор займёт место оригинальной функции и должно вести себя как функция.
попробую объяснить как я сам понимаю декораторы на примере вашего примера:
def func_decor2(some_func):
print('Что-то делаем ДО')
some_func()
print('Что-то делаем после')
по сути здесь вы добавили некий функционал к функции sum_func(), но представьте, что вы добавляете этот функционал к уже существующей функции, которая в разных частях вашего кода вызывается много раз. теперь для того чтобы новый функционал реализовался вам надо в вашем коде заменить все вызовы функции sum_func() на func_decor2(some_func).
в случае же использования декоратора-обертки ничего переписывать не надо, пишите декоратор, обертываете им функцию sum_func() и теперь при каждом ее вызове будет срабатывать декоратор.
def func_decor2(some_func):
def wrapper(*args,**kwargs):
print('Что-то делаем ДО')
some_func(*args,**kwargs)
print('Что-то делаем после')
return wrapper
@func_decor2()
def some_func():
pass
для себя я понял так, что если у меня простой код и мне не сложно что-то добавить и поменять в работе функций, то и декораторы мне не нужны