Пара слов о декораторах

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

def render_to(tmpl):
    def renderer(func):
        def wrapper(request, *args, **kw):
            output = func(request, *args, **kw)
            if not isinstance(output, dict):
                return output
            return render_to_response(tmpl, output, 
                   context_instance=RequestContext(request))
        return wrapper
    return renderer

Конечно, можно дописать пару строк к возврату враппера и получить в принципе работающий механизм:

        wrapper.__name__ = func.__name__
        wrapper.__doc__ = func.__doc__
        return wrapper

Но на самом деле это не самый красивый метод. Куча всяких руководств для начинающих по написанию декораторов всегда рекомендует юзать модуль Мишеля Симионато - decorator. В принципе, всё конечно просто отлично, но есть у него огромный недостаток - это дополнительная библиотека, в то время как есть отличное решение из стандартной библиотеки - functools.wraps. Его использование ничем не отличается от использования decorator‘а:

from functools import wraps

def render_to(tmpl):
    def renderer(func):
        @wraps(func)
        def wrapper(request, *args, **kw):
            output = func(request, *args, **kw)
            if not isinstance(output, dict):
                return output
            return render_to_response(tmpl, output, 
                   context_instance=RequestContext(request))
        return wrapper
    return renderer

И всё становится белым и пушистым. :-) Один момент непонятен - почему его не пишут во всех этих руководствах для начинающих? Ведь эти начинающие с течением времени становятся продолжающими и точно так же не знают о простой и приятной штуке прямо в stdlib‘е.

Comments: 9 (already: 0) Comment post

Молодец, что просвещаешь общественность. Уважаю.

Александр , 20:42

Надо добавить, что это появилось только Python 2.5, поэтому не всем эти радости доступны.

Иван Сагалаев , 19:03 (after 1 day)

Хм. Действительно. Просто у меня сейчас везде вокруг 2.5 (почти тепличные условия, хе-хе :), потому такие моменты иногда проходят мимо внимания. :-)

Alexander Solovyov , 19:57 (after 1 day)

Господа, а можете ли вы пожалуйста либо рассказать, либо дать ссылку на что-то понятное про эти самые декораторы? Что-то никак не могу до конца ухватить суть… Все где-то вокруг да около… Тот же render_to - столько вложений def’ов я так понял нужно, а зачем? Потом вызов декоратора чем-то отличается от простой функции или нет? На сайте Джанги (http://code.djangoproject.com/wiki/CookBookShortcutsPageDecorator) вызов декоратора, аналога render_to, такой потом хитрый… Я так понял, что если нужно задекорировать сущестующую функцию, то пишем так:

render_to_responce = renderer(render_to_responce) и тогда при вызове render_to_responce вызовется декоратор renderer…

если нужно применить декоратор к новой функции, то что-то вроде этого:
@render_to('cool.html') def show_cool_template(request): ...

ух…

NilColor , 18:51 (after 25 days)

Попробую объяснить без теоретизирования (мне самому было в теорию сложно въехать, с практикой лучше пошло), на примере render_to.

Смотри. render_to принимает параметр - имя темплейта, возвращает функцию-обёртку (собственно сам декоратор) - renderer. renderer принимает как параметр функцию (т.е. или my_view = renderer(my_view), или та самая конструкция с @), возвращает ещё одну функцию - wrapper, которая собственно… подменяет собой оригинальную.

Этот wrapper принимает все параметры, которые может принять оригинальная функция, вызывает её, а потом проверяет на то, словарь ли это - если не словарь, то возвращает как есть, если словарь - то возвращает с помощью render_to_response.

Если бы не было той самой первой функции, которая вызывается с именем темплейта, было бы на 1 def меньше.

На тему синтаксиса:

@decorate_all
def some_function(qwe):
    pass

это просто сахар для

def some_function(qwe):
    pass
some_function = decorate_all(some_function)

Т.е. в питоне 2.3 приходилось пользоваться только второй конструкцией. Помимо того, что она и так не сильно приятная, тот же render_to выглядел бы так:

def my_view(request):
    return {'one': 1}
my_view = render_to('my_template.html')(my_view)

Напряжно для понимания. :-)

Надеюсь, всё понятно? :-)

Alexander Solovyov , 12:06 (after 27 days)

вроде все так красиво и понятно написано, но у меня почему-то вываливается ошибка: The view django.contrib.auth.decorators._CheckLogin.call didn’t return an HttpResponse object. может подскажешь из-за чего?

andrej , 12:49 (after 26 days)

Вот просто так сходу - не подскажу… Надо на код смотреть.

Точнее, это характерная довольно ошибка, но я просто не помню, в чём дело. Можешь ещё попробовать на форуме спросить.

Alexander Solovyov , 19:58 (after 29 days)

Александр, сорри за такой спам коментов - я действительно несколько раз пробовал разместить комент (даже в Trac ошибку постил) - но он так ни разу и не разместился, кроме как позавчера. О чем мне и было сказано в письме с просьбой его заапрувить. Что я смог сделать только что… и увидел вот такое…

NilColor , 09:31 (after 27 days)

Да ничего, мне не сложно их поудалять было. Разве что в RSS спама немного пролетело. :-)

Непонятно, почему он сейчас автоматом не заапрувился. Я посмотрю.

Alexander Solovyov , 11:54 (after 27 days)

Comment form for «Пара слов о декораторах»

Required. 30 chars of fewer.

Required.

Comment post