#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) Я бы еще понял декораторы, если бы была возможность вызывать как декорируемую, так и изначальную функцию. Но декораторы, написанные с @ этого не позволяют. Зачем декоратор затирает оригинальную функцию? Буду очень благодарен за ответ с объяснениями и, может быть, ПОНЯТНЫМИ примерами из реальной практики.
Ответы
Ответ 1
Например, в Django декораторы широко используются. Вместо того, чтобы изобретать велосипед и в каждой функции-контроллере, которая должна быть доступна только авторизованным пользователям, писать свой код проверки авторизации, просто декорируем её, соответствующим декоратором. А если нужно ещё гарантировать, что функция будет применяться только для запросов определённого типа? И что вся работа с БД в функции будет выполняться в транзакции? Если весь код тянуть в функцию, то вскоре её изначальный смысл просто потеряется в куче шаблонного кода. А она ведь ещё и не одна в модуле, и для каждой придётся написать этот шаблонный код, повторив его множество раз. Вместо того, чтобы просто написать: @require_POST @login_required @transaction_atomic def some_view(request): ...Ответ 2
В дополнение к ответу @Sergey Gornostaev: Декораторы — по сути реализация аспектно-ориентированного программирования в Питоне. Вы определяете логику или функциональность отдельно от функций, которые будут пользоваться этой функциональностью. Возможно, эту логику разрабатывает для вас другая команда. Пример с логированием, который вы видели практически везде, или те же транзакции — один из возможных примеров аспекта-функциональности. Затем вы влёгкую можете подключить использование этого аспекта без ручного переписывания функций, просто добавлением или убиранием атрибута. В любом случае, атрибут — часть вашего исходного кода. Если вы хотите, чтобы недекорированная функция была доступна, вы можете просто не ставить декоратор. Или иметь отдельно недекорированную функцию, и отдельно её декорированный вариант, который вызывает недекорированный. Или дописать функциональность, которую вносил декоратор, вручную (но в этом случае вам нужно будет обновить ваш код, если в декораторе произойдут изменения, например, багфикс; это обычная проблема дубляжа кода.) Декоратор просто позволяет вам добавлять функциональность легко.Ответ 3
# во первых ссылку на функцию можно получить из декоратора # во вторых теперь эти "куски кода в декораторе" дописать можно к любой функции, например логирование, проверка аргументов перед вызовом def decor(fn): '''декоратор''' def wrapper(*args, **kwargs): if all(args): out = fn(*args, **kwargs) with open('file.txt', 'a') as log: log.write('{} {} {} {}'.format(fn, args, kwargs, out)) print('декоратор', end=' ') return out return wrapper @decor def func(*args): '''@декоратор(изначальная функция)''' return sum(args) func(1, 2, 3) # вызывать как декорируемую original_fn = func.__closure__[0].cell_contents # func без декоратора original_fn(1, 2, 3) # вызывать как изначальную функцию # или import inspect original_fn = inspect.getclosurevars(func).nonlocals['fn'] original_fn(0, 2, 3) # если постоянный вызов дероратора не требуется, изначальную функцию не стоит декорировать @decor def func2(*args): '''изначальная функция''' return sum(args) func2(1, 2, 3) # вызывать как изначальную функцию decor(func2)(1, 2, 3) # вызывать как декорируемую
Комментариев нет:
Отправить комментарий