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

852 просмотра
0
0 Комментариев

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

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

# Декоратор - это функция, ожидающая ДРУГУЮ функцию в качестве параметра
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) Я бы еще понял декораторы, если бы была возможность вызывать как декорируемую, так и изначальную функцию. Но декораторы, написанные с @ этого не позволяют. Зачем декоратор затирает оригинальную функцию?

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


Добавить комментарий

4 Answers

Python Опубликовано 20.08.2019
0

Добрый день друзья. А кто нибудь может обьяснить мне зачем мы вообще определяем еще одну функцию которую потом же и возвращаем??? Я понимаю что так можно, но то что можно не объясняет зачем так делать. Я могу просто написать функцию(декоратор),которая принимает декорирумую, написать какой то код вначале и в конце, а посередине вызвать декорируемую функцию. Вот так:

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

Да и если честно перманентно менять функцию как то не хочется, а вот аргумент  с использованием «__closure__[0].cell_contents» как то не убедил.

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

Помогите пожалуйста, ну реально не догоняю.

Заранее спасибо.

Добавить комментарий
0

# во первых ссылку на функцию можно получить из декоратора
# во вторых теперь эти "куски кода в декораторе" дописать можно к любой функции, например логирование, проверка аргументов перед вызовом
 
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)  # вызывать как декорируемую

Добавить комментарий
0

В дополнение к ответу @Sergey Gornostaev:

Декораторы — по сути реализация аспектно-ориентированного программирования в Питоне.

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

В любом случае, атрибут — часть вашего исходного кода. Если вы хотите, чтобы недекорированная функция была доступна, вы можете просто не ставить декоратор. Или иметь отдельно недекорированную функцию, и отдельно её декорированный вариант, который вызывает недекорированный. Или дописать функциональность, которую вносил декоратор, вручную (но в этом случае вам нужно будет обновить ваш код, если в декораторе произойдут изменения, например, багфикс; это обычная проблема дубляжа кода.)

Декоратор просто позволяет вам добавлять функциональность легко.

Добавить комментарий
0

Например, в Django декораторы широко используются. Вместо того, чтобы изобретать велосипед и в каждой функции-контроллере, которая должна быть доступна только авторизованным пользователям, писать свой код проверки авторизации, просто декорируем её, соответствующим декоратором. А если нужно ещё гарантировать, что функция будет применяться только для запросов определённого типа? И что вся работа с БД в функции будет выполняться в транзакции? Если весь код тянуть в функцию, то вскоре её изначальный смысл просто потеряется в куче шаблонного кода. А она ведь ещё и не одна в модуле, и для каждой придётся написать этот шаблонный код, повторив его множество раз. Вместо того, чтобы просто написать:

@require_POST
@login_required
@transaction_atomic
def some_view(request):
    ...

Добавить комментарий
Напишите свой ответ на данный вопрос.
Scroll Up

Подпишись на канал!

Новые видеоуроки, книги и полезные статьи для python программистов!