Регистрация - Вход

Наши спонсоры


Диспетчеризация событий в Python


Если вы смотрели исходный код Django, вы могли заметить такие строки.:

from django.dispatch import dispatcher

......

    # ниже

    dispatcher.send(signal=signals.pre_init, sender=self.__class__, args=args, kwargs=kwargs)

Что они делают? Это - вызовы функций библиотеки PyDispatcher, которая с некоторых пор поставляется вместе с Django и используется для диспетчеризации событий в процессе исполнения программы на языке Python.

Если вы не знакомы с концепцией программирования обработки событий, но при этом программировали на JavaScript, то наверняка диспетчеризацией событий пользовались. Например, присваивая атрибуту onclick DOM-объекта в качестве значения функцию, вы тем самым назначаете эту функцию обработчиком события щелчка кнопкой мыши по этому объекту. Очень удобно — не нужно назначать каких-либо глобальных переменных и проверять их значение. Код сработает тогда, когда произойдет ожидаемое обработчиком событие.

Аналогичным образом работает и PyDispatcher. В качестве события может служить почти любой объект Python, единственное требование — должна быть возможность хэшировать этот объект, и это не должен быть None. В качестве обработчика - любой объект, который можно вызвать на исполнение (callable) — функция, метод объекта или класса, объект с определенным методом call.

Рассмотрим пример. У нас есть некий объект, который хранит в своем свойстве значения счетчика вызова функций. Счетчик может меняться в результате вызова произвольных функций в произвольных местах приложения, но при этом мы не хотим, чтобы каждая функция, число вызовов которой мы хотим знать, знала о том, как мы обрабатываем изменение счетчика и где его храним — пусть этим занимается специальный объект. Кроме того, нам может понадобиться известить от том, что функция вызвана, какой-либо другой объект, про который объект-счетчик ничего не знает. Каким образом нашему объекту дать понять, что счетчик изменился, но при этом не отслеживая во всех функциях, о вызове которых нам надо знать, все объекты, которым нужна информация о том, что функция была вызвана? При помощи обработчика событий и библиотеки PyDispatcher. Допустим, мы в среде Django, и PyDispatcher доступен в модуле django.dispatch.

'''module functioncallcounter'''

from django.dispatch import dispatcher

'''simple object to use as an event'''

FunctionCallEvent = object()

class FunctionCallCounter(object):

    '''function call counter object'''

    counters = None

    def __init__(self):

        '''initialize the counters and subscribe to event FunctionCallEvent'''

        self.counters = {}

        dispatcher.connect(self.functionCallHandler, FunctionCallEvent)

    def __del__(self):

        '''destructor - unsubscribe to event FunctionCallEvent'''

        dispatcher.disconnect(self.functionCallHandler, FunctionCallEvent)

    def functionCallHandler(self, *args, **kwargs):

        '''FunctionCallEvent handler'''

        sender = kwargs['sender'].__name__

        if not self.counters.has_key(sender):

            self.counters[sender] = 0

        self.counters[sender] += 1

Counter = FunctionCallCounter()

Мы определили класс FunctionCallCounter в модуле functioncallcounter (файл будет называться functioncallcounter.py и находиться где-то в sys.path). Свойство counters экземпляра этого класса будет содержать словарь, ключами которого будут имена вызванных функций, значениями — число вызовов этих функций. Метод functionCallHandler при инициализации экземпляра класса подписывается на событие, в качестве которого используется объект FunctionCallEvent при помощи вызова функции dispatcher.connect (первый аргумент — обработчик события, второй — событие, которое мы хотим обработать). При удалении нашего объекта-счетчика вызывается метод del, в котором производится отмена регистрации обработчика события при помощи dispatcher.disconnect. В принципе, это необязательно, т.к. диспетчер хранит ссылки на обработчики событий как слабые ссылки (модуль weakref), но мы все же подчистим за собой.

Обратите внимание, что словарем свойство counters становится только при инициализации объекта. Свойство же класса определено как None. Это сделано намеренно. Не буду сейчас вдаваться в подробности, только лишь скажу, что стоит избегать в обявлении свойств класса присваивания этим свойствам изменяемых (mutable) объектов, таких типов как list, dict, экземпляров классов, за исключением тех случаев, когда вы точно знаете что хотите и как это будет работать. То же относится и к объявлению значений аргументов функций по умолчанию.

И как же использовать то, что мы только что написали? Допустим, есть функция, число вызовов которой мы хотим посчитать. Сделаем следующее:

from functioncallcounter import FunctionCallEvent

from django.dispatch import dispatcher

def counted():

    '''This function wants to be counted'''

    dispatcher.send(FunctionCallEvent, counted)

    ### do some useful things below

Все очень просто. Мы используем dispatcher.send. Первый аргумент — событие, второй — значение, которое будет передано обработчику в ключевом аргументе sender, в нем мы передаем саму вызвавшую функцию. Функции dispatcher.send можно также передать произвольное число других, как позиционных, так и ключевых аргументов, и они будут переданы обработчику. Единственное, что надо быть осторожными с позиционными аргументами — их может оказаться больше, чем функция-обработчик в состоянии принять. Из ключевых же аргументов, переданных dispatcher.send, функции-обработчику диспетчером передаются только те, которые она принять в состоянии.

Соответственно, в любой момент мы можем прочитать значения счетчиков:

from functioncallcounter import Counter

print Counter.counters

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

В следующей статье я поговорю о диспетчеризации событий между процессами.

Источники:

www.ragbag.ru/2006/09/25/pydispatcher/


КОММЕНТАРИИ







Теги


RSS

Архив