А вы поймали новые сигналы?

Вчера джанга сделал ещё один существенный шаг на пути к долгожданному релизу 1.0. На этот раз посчастливилось обновиться инфраструктуре сигналов. Основное и главное отличие новой подсистемы сигналов - это почти двукратное увеличение производительности.

Исторически сигналы в джанге были сделаны на базе пакета pyDispatcher. который почти без изменений был скопирован в django.dispatch. Со временем выяснилось, что он избыточен и медленен.

Сигналы практически везде пронизывают код джанги. Повесить обработчик можно и на изменение объекта, и на начало и конец обработчик запроса и рендеринг шаблона и много чего ещё. Причем они не только предоставляют некий интерфейс взаимодействия с внешним миром, но и активно применяются внутри самой джанги. Например, удаление загруженных файлов вслед за объектами и закрытие соединения с базой при завершении обработки запроса(!).

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

Возможности погонять тесты у меня нет, так что поверим разработчикам на слово. Лучше копнем поглубже в суть.

Конечно, такие большие перемены сказались и на внешнем интерфейсе подсистемы сигналов, поэтому данные изменения обратно не совместимы.

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

Первым делом объявим сигнал:

from django.dispatch import Signal

dinner_is_served = Signal()

Первое что бросается в глаза, это то что сами сигналы теперь полноценные классы. С одной стороны это более логично, но с другой красивые и элегантные решения, вроде использования в качестве идентификаторов сигналов строковые литералы, уже не пройдут. Сигнал это полноценная сущность которую надо предварительно объявить где-то.

Как мы знаем, сигналы обычно обозначают наступление какого-либо события. События происходят в каком-то окружении, а следовательно вместе с сигналами распространяется некая мета информация, описывающая как раз контекст его возникновения. Теперь у разработчика есть явная возможность помимо стандартного sender описать ещё дополнительные параметры, с которыми сигнал должен посылаться. Выглядит это так:

dinner_is_served = Signal(providing_args=["courses", "dessert"])

Т.е. по логике при посылке данного сигналы должна быть указано подмножество этих аргументов. Но это нигде не проверяется и получается что это всего лишь такой вариант самодокументирования:) Допилят позже надо полагать.

Ну всё, сигнал объявили и надо его послать всем желающим:

dinner_is_served.send(sender="Barrymore", courses=["soup", "oatmeal porridge"], dessert="pudding")

Добавить то желающих тоже просто:

def sherlock_holmes(sender, courses, dessert, **kwargs):
   pass

dinner_is_served.connect(sherlock_holmes)

Как легко заметить, все операции по присоединению хендлеров и посылки происходят с самим объектом событий. Но для некоторой обратной совместимости, сохранились и старые методы через диспетчер dispatcher.send и dispatcher.connect, но их уже не рекомендуется использовать.

Итак, подведем итог. Для того чтобы максимально просто и безболезненно заставить работать старый код после очередного svn up джанги, надо явным образом создавать объект сигнала. Всё остальное должно работать. Если конечно вы не использовали какие-то более изощренные способы работы с сигналами.

Избавиться совсем от наследия pyDispatcher не получилось. Был оставлен модуль saferef.py в котором реализована "слабая ссылка"(weak reference) и по умолчанию обработчики, присоединяемые к сигналу, таковыми являются. Но это поведение можно изменить, добавив при присоединении хендлера параметр weak=False.

Кстати, косвенно с этим связанна ещё одна интересная особенность подсистемы сигналов.

Представим, что у нас есть код:

def some_func(param):
   def handler(*args, **kwargs):
      pass

   some_signal.connect(handler)

И потом вы эту some_func зовете несколько раз. И когда сигнал сработает, что должно произойти? Правильно. Ничего. Хендлер не сработает потому что из-за "слабой" ссылки GC его убьет, а то что мы его зарегистрировали его не волнует. Поэтому добавляем weak=False:

some_signal.connect(handler, weak=False)

А что теперь будет? Ага, обработчик сработает несколько раз, точнее столько раз сколько мы вызвали функцию some_func в рамках жизни данного процесса. Для того чтобы избежать повторного добавления обработчика, есть очень полезный параметр dispatch_uid, который, если присутствует, должен быть уникальным идентификатором данного хендлера - строка или что угодно hashable. Вот так например:

some_signal.connect(handler, weak=False, dispatch_uid="my_useful_unique_handler")

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

Шаг очередной джанга сделала, мы его обсудили и ждем следующего. А это "file storage" между прочим - тоже веха!

Обновление:

Произошло одно маленькое изменение - хендлеры сигналов должны обязательно принимать **kwargs, а то получите эксепшен при попытке его присоединить

Комментарии 13

  1. Kirill Mavreshko написал:

    С замиранием сердца слежу за предрелизными django'трясениями. И после каждого сначала ликую, а потом с ужасом оцениваю "причинённый ущерб" :) Сколько часов теперь пройдут в рефакторинге написанного за последние пару лет... ужас! Теперь вот ещё и завязанный на сигналы код...

    И всё же, я рад! :)

    Спасибо за статью и хорошие новости!

    Оставлен 07 Август 2008 в 16:56
  2. Сергей Тарковский написал:

    "Первое что бросается в гала, это то что сами сигналы теперь полноценный классы" - слющай, дарагой, сыгнал харащо писать, да?

    А за статью спасибо :)

    Оставлен 07 Август 2008 в 17:54
  3. Yury Yurevich написал:

    typo: Хотя многие разработчики и не особо их используют в своём коже...

    Оставлен 07 Август 2008 в 18:17
  4. Александр Кошелев написал:

    "Первое что бросается в гала, это то что сами сигналы теперь полноценный классы" - слющай, дарагой, сыгнал харащо писать, да?

    Эх, да. Спасибо.

    Оставлен 07 Август 2008 в 18:56
  5. Kirill Mavreshko написал:

    А может http://orphus.ru/? Чтобы не писать про мелкие ляпы в комментариях.

    Оставлен 07 Август 2008 в 19:53
  6. Александр Кошелев написал:

    А может http://orphus.ru/? Чтобы не писать про мелкие ляпы в комментариях.

    Не:) Мне больше другие варианты нравятся:

    -Прикрутить typo reporter к движку блога

    -Или прокачать как-то скил typo resistance

    Но серьезно говоря, просто надо ещё один лишний раз прочитывать - что и буду делать:)

    Оставлен 07 Август 2008 в 20:29
  7. Александр Кошелев написал:

    Обновление:

    Произошло одно маленькое изменение - хендлеры сигналов должны обязательно принимать **kwargs, а то получите эксепшен при попытке его присоединить

    Оставлен 08 Август 2008 в 13:19
  8. Big 40wt Svetlyak написал:

    Интересно, зачем им понадобилось тащить из pyDispatcher saferef.py, если есть стандартный weakref?

    Оставлен 10 Сентябрь 2008 в 16:50
  9. Александр Кошелев написал:

    Интересно, зачем им понадобилось тащить из pyDispatcher saferef.py, если есть стандартный weakref?

    Там в комментах:

    Provides a way to safely weakref any function, including bound methods (which aren't handled by the core weakref module).

    Оставлен 11 Сентябрь 2008 в 10:15
  10. Big 40wt Svetlyak написал:

    Понятно.

    Оставлен 11 Сентябрь 2008 в 10:38
  11. Glader написал:

    Мне сигналы не очень нравятся, потому что снижают ясность кода. Непонятно, что будет вызвано при конкретном событии. Я один такой?

    Оставлен 02 Декабрь 2008 в 22:18
  12. Александр Кошелев написал:

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

    Оставлен 03 Декабрь 2008 в 00:03
  13. Glader написал:

    Понятно что упрощают :) Но меня очень смущает мысль об отладке кода с неочевидными зависимостями, когда неизвестно, что (и в каком порядке) будет вызвано в результате каких-то действий. Может просто привыкнуть надо...

    Оставлен 03 Декабрь 2008 в 00:37