Одним из самых сложных для понимания и осознания элементов языка является декоратор, хотя по сути это очень простая вещь, доступная для понимания даже начинающему программисту. Новых Эверестов я не открываю, а лишь предлагаю краткий обзор возможностей и несколько типичных примеров использования. Этакий короткий экскурс в метапрограммирование на питоне.

Upd 1: изменил несколько категоричное утверждение о несходстве паттерна Декоратор и одноимённой языковой конструкции на более мягкое.


В самом начале хочется отметить, что рассматриваемый здесь декоратор (decorator) как элемент языка Python не является реализацией одноимённого паттерна проектирования, его возможности гораздо шире, хотя сам паттерн и может быть реализован через питоновский декоратор.


Что такое декоратор и простейшие способы его использования

Итак, декоратор — это удобный способ изменения поведения некоторой функции (а начиная с Python 2.6 и 3.0 и целого класса). С точки зрения синтаксиса выглядит достаточно просто. Например, следующий фрагмент кода, использующий декоратор:

@f1
def func(x): pass


эквивалентен такому:

def func(x): pass
func = f1(func)


Слово «эквивалентен» нужно понимать буквально: операция выполняется в момент определения функции один раз и если f1 вернёт, скажем, None, то в переменной func будет записан None. Простой пример (декорирующая функция возвращает None, в итоге func тоже оказывается равным None):

def empty(f):
    return None

@empty
def func(x, y):
    return x + y

print func # напечатает: None


Давайте рассмотрим более практичный пример. Допустим, нужно проверить, как быстро работает некоторая функция, нужно знать, сколько времени ��тнимает каждый её вызов. Задача элементарно решается при помощи декоратора.

import time
def timer(f):
    def tmp(*args, **kwargs):
        t = time.time()
        res = f(*args, **kwargs)
        print "Время выполнения функции: %f" % (time.time()-t)
        return res

    return tmp

@timer
def func(x, y):
    return x + y

func(1, 2) # напечатает что-то типа: Время выполнения функции: 0.0004


Как видно из примера, чтобы заставить функцию func при каждом исполнении печатать время работы, достаточно «обернуть» её в декоратор timer. Закомментируем строчку «@timer» и func продолжает работать как обычно.

Функция timer() является самым типичным декоратором. В качестве единственного своего параметра она принимает функцию, внутри себя создаёт новую функцию (в нашем примере с именем tmp), в которой добавляет какую-либо логику и эту самую новую функцию возвращает. Обратите внимание на сигнатуру функции tmp() — tmp(*args, **kwargs), это стандартный способ «захватить» все возможные аргументы, таким образом, наш декоратор пригоден для функций с совершенно произвольной сигнатурой.

Функцию можно обернуть в несколько декораторов. В этом случае они «выполняются» сверху вниз. Например, создадим декоратор pause(), который будет делать паузу в одну секунду перед исполнением функции.

import time

def pause(f):
    def tmp(*args, **kwargs):
        time.sleep(1)
        return f(*args, **kwargs)

    return tmp


И определим функцию func следующим образом (используя сразу два декоратора — pause и timer):

@timer
@pause
def func(x, y):
    return x + y


Теперь вызов func(1, 2) покажет общее время исполнения примерно одну секунду.

Более сложное использование декораторов


Вам могло показаться, что в качестве декоратора можно использовать только функцию. Это не так. В качестве декоратора может выступать любой объект, который можно «вызвать». Например, в качестве декоратора может выступать класс. Вот значительно более сложный пример, показывающий, как можно конструировать потоки (threads) при помощи декораторов:

import threading

class Thread(threading.Thread):
    def __init__(self, f):
        threading.Thread.__init__(self)
        self.run = f

@Thread
def ttt():
    print "This is a thread function"

ttt.start()


Давайте разберём подробно этот пример. «Классический» способ создания класса потока следующий: создаётся новый класс, наследник класса threading.Thread (threading — это стандартный модуль из Питона для работы с потоками); в классе задаётся метод run(), в который помещается непосредственно код, который нужно выполнить в отдельном потоке, затем создаётся экземпляр этого класса и для него вызывается метод start(). Вот как бы это выглядело в «классическом» варианте:

class ThreadClassic(threading.Thread):
    def run(self):
        print "This is a thread function"

ttt = ThreadClassic()
ttt.start()


В нашем же случае декорируемая функция передаётся в качестве аргумента конструктору класса потока, где присваивается компоненту класса run.

Для создания нескольких разных потоков вам нужно дважды продублировать «классический» код. А при использовании «потоковых» декораторов — только добавить вызов декоратора к функции потока.

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


В декоратор можно передавать параметры, запись вида:

@f1(123)
def func(): pass

эквивалентна

def func(): pass
func = f1(123)(func)


По сути это означае, что декоратором является результат выполнения функции f1(123). Давайте напишем обновлённый декоратор pause(), который позволяет указывать величину паузы перед выполненением оборачиваемой функции:

import time

def pause(t):
    def wrapper(f):
        def tmp(*args, **kwargs):
            time.sleep(t)
            return f(*args, **kwargs)
        return tmp

    return wrapper

@pause(4)
def func(x, y):
    return x + y

print func(1, 2)

Обратите внимание, как декоратор фактически создаётся динамически внутри функции pause().

Использование декораторов в классах


Использование декораторов на методах классов ничем не отличается от использования декораторов на обычных функциях. Однако для классов есть предопределённые декораторы с именами staticmethod и classmethod. Они предназначены для задания статических методов и методов класса соответственно. Вот пример их использования:

class TestClass(object):
    @classmethod
    def f1(cls):
        print cls.__name__

    @staticmethod
    def f2():
        pass

class TestClass2(TestClass):
    pass

TestClass.f1() # печатает TestClass
TestClass2.f1() # печатает TestClass2

a = TestClass2()
a.f1() # печатает TestClass2


Статический метод (обёрнутый декоратором staticmethod) в принципе соответствует статическим методам в C++ или Java. А вот метод класса — это нечто более интересное. Первым аргументом такой метод получает класс (не экземпляр!), это происходит примерно так же, как с обычными методами, которые первым аргументом получают референс на экземпляр класса. В случае, когда метод класса вызывается на инстансе, первым параметром передаётся актуальный класс инстанса, это видно на примере выше: для порождённого класса передаётся именно порождённый класс.

Где ещё можно использовать декораторы

Список потенциальных областей применения декораторов очень большой:

Настоящие трудности


Использовать декораторы нужно весьма осторожно, хорошо осознавая, чего именно вы хотите добиться. Излишнее их использование приводит к появлению слишком сложного для понимания кода. Можно в приступе озарения написать такое, что позже сам не разберёшься, как же написанное работает.

Использование декоратора ломает documentation strings для метода/функции. Проблему можно решить, вручную «пробрасывая» значение __doc__ в создаваемую внутри декоратора функцию. А можно воспользоваться замечательным модулем с неожиданным названием decorator, который помимо поддержки doc strings, умеет ещё множество других полезных вещей.

Рекомендуемая литература

  1. http://www.phyast.pitt.edu/~micheles/python/documentation.html — страница документации модуля decorator
  2. www.ibm.com/developerworks/linux/library/l-cpdecor.html — статья про декораторы на IBM developerWorks (и перевод на русский)
  3. Mark Lutz: Learning Python, 3rd Edition, Chapter “Function Decorators”
  4. PEP 318: Function Decorators
  5. PEP 3129: Class Decorators (начиная с Python 2.6 и Python 3.0)
    wiki.python.org/moin/PythonDecorators — статья из официальной Python wiki про декораторы