ГЛАВНАЯ      ДОКУМЕНТАЦИЯ      СТАТЬИ      ПРОГРАММЫ      ССЫЛКИ      ФОРУМ      ДРУГОЕ   

Декораторы функций и методов в Python

Начиная с версии 2.4 в Python введена новая синтаксическая конструкция — декоратор функции/метода. С ее помощью можно "декорировать" функции — дополнять их новой функциональностью без внесения изменений в сами функции и методы. Слово "декоратор" часто используется в связи с паттеном Декоратор, описанным в знаменитой книге "Банды четырех". Это не совсем тот случай, имейте в виду, хотя некоторое сходство имеется. В этой статье я опишу механизм декораторов и рассмотрю примеры его применения.
Прежде всего, стоит отметить, что функции в Python — это first-class objects. Что это означает, можно почитать на странице по ссылке или в другой литературе (неплохая дискуссия по поводу этого термина). На русский язык это переводится с трудом, т.к. слово "первоклассный" у нас имеет несколько другой оттенок, хотя и передает частично то значение, которое подразумевается в данном случае. На практике это означает, что такой объект не имеет ограничений по использованию — он может иметь атрибуты, может быть присвоен переменной, передан в функцию в качестве аргумента, может быть значением возврата из функции и т.п. В отличие от PHP, где передача функции осуществляется при помощи синтаксических хаков с разыменованием переменных, в Python передается непосредственно ссылка на объект-функцию.
При этом, объект функции подчиняется тем же правилам, которым подчиняется любой другой объект в Python — в частности, на него заводится счетчик ссылок, и сам объект не зависит от того первоначального имени, которое было ему дано при определении функции (этому имени может быть впоследствие присвоен другой объект, но объект функции будет доступен по другим ссылкам, если они есть).
Ну и, конечно, функция может быть создана в процессе исполнения другой функции. Все эти особенности и используются для декорирования функций.

Декорирование функций

Функция может быть передана в качестве аргумента в другую функцию, а имени, которое указывает на функцию, можно присвоить другой объект. Это открывает нам широкие возможности писать код в стиле функционального программирования. Мы можем сделать следующее:
def foo(arg):
    print arg

def decorated(fun):
    if not hasattr(fun, '__call__'):
        raise TypeError ('The argument should be a callable')
    def wrapper(arg):
        print "calling function %s with arg %s" % (fun.__name__, str(arg))
        return fun(arg)
    return wrapper

foo = decorated(foo)

foo(1)
Результат запуска примера будет выглядеть так:
calling function foo with arg 1
1
Функция foo просто выводит переданный ей аргумент. Функция decorated (позже станет понятно откуда название) принимает в качестве аргумента функцию (точнее, объект, который может быть вызван на исполнение). Если будет передан какой-нибудь неподходящий объект, будет вызвана исключительная ситуация TypeError. Внутри функции decorated объявляется функция wrapper, которая "оборачивает" вызов переданной в decorated функции, предваряя ее вызов выводом имени вызываемой функции и ее аргумента. Функция wrapper — фактически шаблон для создания новой функции, которая возникнет только в процессе исполнения decorated.
Эта новая функция — и есть результат вызова decorated, и мы присваиваем его имени foo. После этого foo ссылается не на оригинальную функцию foo, а на эту новую функцию, которая была создана из определения wrapper в процессе исполнения decorated(foo). На саму оригинальную foo осталась ссылка только внутри этой созданной функции.
Здесь также использован такой механизм языка, как вложенные области видимости (nested scopes) — если переменная доступна в локальной области видимости функции, то она будет доступна также в локальной области видимости внутри блока другой функции, находящегося в блоке этой функции, если эта переменная там не переопределена. Т.о., аргумент fun функции decorated доступен внутри функции wrapper, определенной в блоке decorated.
Основное достоинство такого приема в том, что функцию decorated можно применять к любой функции, при этом не внося изменений в эти функции. Сделаем что-нибудь более полезное, чем выводить имя функции и аргумент. Например, добавить проверку допустимости аргумента функции.
def require_int(fun):
    if not hasattr(fun, '__call__'):
        raise TypeError ('The argument should be a callable')
    def wrapper(arg):
        if not isinstance(arg, int):
            raise TypeError ('The argument should be an integer')
        return fun(arg)
    return wrapper
Применив функцию require_int к любой функции ( somefunc = require_int(somefunc) ), в которой мы хотим в качестве аргумента видеть только целое число, мы тем самым обеспечим такую проверку, и нам не нужно кодировать такую проверку внутри всех таких функций.
Фактически, этот прием — и есть декорирование функций, и доступен в Python задолго до версии 2.4. Функции decorated и require_int — это и есть декораторы. В версии 2.4 лишь появилась специальный синтаксис, упрощающий декорирование.

Синтаксис декораторов функций

Синтаксис декорирования был заимствован из синтаксиса аннотаций Java. Для того, чтобы декорировать функцию, нужно перед ее объявлением указать имя функции-декоратора, предваряя его символом @. В нашем случае, выражение foo = decorated(foo) с помощью нового синтаксиса можно записать так:
@decorated
def foo(arg):
    print arg
В функцию decorated никаких изменений вносить не надо.
Само собой, мы можем захотеть декорировать нашу функцию несколькими декораторами. Например, нам надо проверять, чтобы аргумент был целым числом, и при этом положительным целым. Мы можем записать это так:
def require_positive(fun):
    if not hasattr(fun, '__call__'):
        raise TypeError ('The argument should be a callable')
    def wrapper(arg):
        if arg <= 0:
            raise ValueError ('The argument should be a positive integer')
        return fun(arg)
    return wrapper 

foo = require_positive(foo)
foo = require_int(foo)

А можем и так:
@require_int
@require_positive
def foo(arg):
    print arg
Обратите внимание на порядок записи декораторов: это выражение эквивалентно foo = require_int(require_positive(foo)). Это важно, т.к. порядок применения декораторов часто имеет значение. В нашем случае сначала мы должны проверить целочисленность аргумента, т.е. функция, созданная в require_int должна быть вызвана прежде, чем функция, созданная в require_positive проверит, положительное ли число мы получили в аргументе.
Как мы видим, при декорировании без использования специального синтаксиса выражения пишутся в обратном порядке — foo будет ссылаться на результат последнего выражения, т.е. require_int отработает первым. Т.о., специальный синтаксис также более удобен, т.к. мы перечисляем декораторы в прямом порядке.
Кроме того, такой синтаксис позволяет передачу декоратору аргументов. Например, мы хотим, чтобы аргумент был не менее какого-либо числа.
@require_int
@require_minimum(20)
def foo(arg):
    print arg
Стоп. Декоратор у нас принимает только один аргумент - функцию, которую мы декорируем. Каким образом нам написать такой декоратор, который принимает другие аргументы? Приведенное выше выражение эквивалентно такой записи: foo = require_int(require_minimum(20)(foo)). Т.е. декоратор здесь - не сама функция require_minimum, а результат ее вызова с аргументом minimum, который должен быть функцией, принимающей в качестве аргумента другую функцию. Напишем эту матрешку.
def require_minimum(minimum):

    def decorator(fun):

        def wrapper(arg):
            if arg 
Функция require_minimum содержит определение функции decorator, которое, в свою очередь, содержит определение функции wrapper. У нас добавилась еще одна функция — decorator, которая принимает в качестве аргумента функцию, и из которой, в сущности, и будет создана функция-декоратор. Функция require_minimum оборачивает ее с той целью чтобы значение минимума передалось декоратору через nested scopes.
Само собой, require_minimum - это обычная функция, и может быть использована для создания специализированных декораторов, которые уже не потребуют аргумента minimum, а будут проверять минимальное значение в соответствие с переданным require_minimum аргументом.
Например, наш декоратор require_positive - это частный случай require_minimum, соответственно, для того, чтобы получить эквивалентный по функциональности декоратор, мы можем сделать так:
require_positive = require_minimum(1)
Как верно замечает Макс Ищенко, декоратором может служить любой объект, который можно вызвать на исполнение. Такой объект можно сконструитовать, определив в его классе метод __call__. Таким образом, require_minimum можно записать и так:
class require_minimum(object):
    def __init__(self, minimum):
        self.minimum = minimum

    def __call__(self, fun):
        def wrapper(arg):
            if arg 
В этом случае аргумент minimum передается не функции-"матрешке", а конструктору класса. Получившийся объект будет иметь метод __call__, который и будет нашим декоратором.

Применение декораторов

Декораторы применяются достаточно широко. Сфера применения - проверка допустимости аргументов, перехват и изменение аругментов функций, перехват и изменение значений возврата функций, проверка дополнительных условий и т.п. Рассмотрим несколько примеров.

classmethod, staticmethod

При объявлении методов классов поведением по умолчанию будет оборачивание функций в объекты методов, которые при вызове вызывают исходные функции, передавая в качестве первого аргумента экземпляр класса. Подробнее об этом можно почитать у меня в статье про дескрипторы.
Это поведение можно переопределить — как и почти все в Python :)
Если объявление метода предварить декоратором classmethod, то будет создан объект-метод, который в первом аргументе функции будет передавать не экземпляр класса, а сам класс.
class X(object):
    @classmethod
    def myClassMethod(cls):
        print cls
При вызове метода myClassMethod как через сам класс X, так и через экземпляр этого класса, в переменная cls будет ссылаться на класс X.
Если объявление метода предварить декоратором staticmethod, то будет создан объект-метод, который никак не будет изменять список аргументов, переданный методу. Метод будет вызван как будто это обычная функция, не имеющая представления о состоянии класса или экземпляра класса.

Django: проверка аутентификации

Для проверки аутентификации в Django есть удобный декоратор login_required. Если мы хотим, чтобы функция-view выполнилась только в том случае, если пользователь прошел процедуру аутентификации, достаточно задекорировать наш view при помощи login_required. Если пользователь не аутентифицирован, то он будет перенаправлен на страницу логина.
from django.contrib.auth.decorators import login_required
@login_required
def my_view(request):
    # do something
Декоратор login_required определен следующим образом:
def user_passes_test(test_func, login_url=LOGIN_URL):
    def _dec(view_func):
        def _checklogin(request, *args, **kwargs):
            if test_func(request.user):
                return view_func(request, *args, **kwargs)
            return HttpResponseRedirect('%s?%s=%s' % (login_url, REDIRECT_FIELD_NAME, quote(request.get_full_path())))
        _checklogin.__doc__ = view_func.__doc__
        _checklogin.__dict__ = view_func.__dict__

        return _checklogin
    return _dec

login_required = user_passes_test(lambda u: u.is_authenticated())
В данном случае функция _dec внутри user_passes_test по назначению соответствует функции decorator, а функция _checklogin — wrapper в нашей require_minimum. Функция _checklogin оборачивает наш view и вызывает его только если переданная user_passes_test тестовая функция возвращает истинное значение, в противном случае производится редирект.
Заметим, что атрибутам функции _checklogin присваиваются атрибуты исходного view, т.к. место view занимает уже другая функция. Если этого не делать, арибуты исходной функции будут недоступны.

Django: управление транзакциями в базе данных

Для управления транзакциями в Django существует несколько декораторов. Рассмотрим декоратор commit_on_success.
from django.db.transaction import commit_on_success
@commit_on_success
def my_func():
    # do something

Код функции:
def commit_on_success(func):
    def _commit_on_success(*args, **kw):
        try:
            enter_transaction_management()
            managed(True)
            try:
                res = func(*args, **kw)
            except Exception, e:
                if is_dirty():
                    rollback()
                raise
            else:
                if is_dirty():
                    commit()
            return res
        finally:
            leave_transaction_management()
    return _commit_on_success
Декоратор commit_on_success начинает транзакцию перед выполнением исходной функции, если транзакция еще не начата, и выполняет функцию в блоке try ... except. В случае возникновения в функции исключительной ситуации транзакция откатывается. В случае успешного завершения функции (критерий успешности — отсутствие исключительных ситуаций) транзакция фиксируется.
Оригинальный PEP про декораторы функций и методов.

Источники:
        https://www.ragbag.ru/2006/11/07/python_decorators/

Автор: Деркачев М.   
ПОМОЩЬ САЙТУ :
sms.Є®ЇЁ«Є  *PythonUA*
Copyright © 2006 python.com.ua