• 3.1
  • 3.2
  • 5.0
  • Версия документации: 6.1

Сигналы

Django включает в себя «диспетчер сигналов», который помогает развязанным приложениям получать уведомления, когда действия происходят в другом месте платформы. Короче говоря, сигналы позволяют определенным отправителям уведомить группу получателей о том, что произошло какое-то действие. Они особенно полезны, когда одни и те же события могут интересовать множество фрагментов кода.

Например, стороннее приложение может зарегистрироваться, чтобы получать уведомления об изменении настроек:

from django.apps import AppConfig
from django.core.signals import setting_changed


def my_callback(sender, **kwargs):
    print("Setting changed!")


class MyAppConfig(AppConfig):
    ...

    def ready(self):
        setting_changed.connect(my_callback)

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

Вы также можете определять и отправлять свои собственные сигналы. См. «Определение и отправка сигналов» ниже.

Предупреждение

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

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

Прослушивание сигналов

Для того, чтобы принять сигнал, Вам необходимо с помощью метода Signal.connect() зарегистрировать функцию receiver, которая вызывается, когда сигнал послан.

Signal.connect(receiver, sender=None, weak=True, dispatch_uid=None)
Параметры:
  • receiver – Функция, которая будет привязана к этому сигналу. Смотрите Функции-получатели.

  • sender – Указывает конкретного отправителя. Смотрите Сигналы, получаемые от определённых отправителей..

  • weak – Django сохраняет обработчики сигналов используя слабые ссылки(weak references). Поэтому, если функция-обработчик является локальной функцией, сборщик мусора может удалить ее. Чтобы избежать этого, передайте weak=False в connect().

  • dispatch_uid – Уникальный идентификатор получателя сигнала. На случай, если назначение обработчика может вызываться несколько раз. Смотрите Предотвращение дублирования сигналов.

Давайте посмотрим, как это работает, зарегистрировав сигнал request_finished, который вызывается после завершения выполнения HTTP запроса.

Функции-получатели

Во-первых, мы должны определить функцию-получатель. Получатель должен быть Python функцией или методом:

def my_callback(sender, **kwargs):
    print("Request finished!")

Заметьте, что функция принимает аргумент sender, а также аргументы (**kwargs) в формате словаря; все обработчики сигналов должны принимать подобные аргументы.

Мы рассмотрим отправителей чуть позже, а сейчас взглянем на аргумент **kwargs. Все сигналы отправляют аргументы ключевого слова и могут изменить эти аргументы ключевого слова в любое время. В случае request_finished он документирован как не отправляющий аргументов, что означает, что у нас может возникнуть соблазн написать нашу обработку сигнала как my_callback(sender).

Это было бы неверно – на самом деле, Django выдаст ошибку, если Вы это сделаете. Так произойдёт, потому что при любом вызове аргументы могут быть добавлены к сигналу и получатель должен быть в состоянии обработать эти новые аргументы.

Получателями также могут быть асинхронные функции с той же сигнатурой, но объявленные с использованием async def:

async def my_callback(sender, **kwargs):
    await asyncio.sleep(5)
    print("Request finished!")

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

Регистрация функции-получателя

Есть два способа, которыми Вы можете подключить получатель к сигналу. Вы можете вручную вызвать connect:

from django.core.signals import request_finished

request_finished.connect(my_callback)

Кроме того, вы можете использовать декоратор receiver() при определении вашего получателя:

receiver(signal, **kwargs)
Параметры:
  • signal – Сигнал или список обрабатываемых сигналов.

  • kwargs – Аргументы подстановочного ключевого слова для передачи в function.

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

from django.core.signals import request_finished
from django.dispatch import receiver


@receiver(request_finished)
def my_callback(sender, **kwargs):
    print("Request finished!")

Теперь наша функция my_callback будет вызываться каждый раз, когда запрос завершается.

Куда положить этот код?

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

На практике обработчики сигналов обычно определяются в подмодуле signals приложения, к которому они относятся. Приемники сигналов подключаются в методе ready() вашего приложения класс конфигурации. Если вы используете декоратор receiver(), импортируйте подмодуль signals внутри ready(), это неявно подключит обработчики сигналов:

from django.apps import AppConfig
from django.core.signals import request_finished


class MyAppConfig(AppConfig):
    ...

    def ready(self):
        # Implicitly connect signal handlers decorated with @receiver.
        from . import signals

        # Explicitly connect a signal handler.
        request_finished.connect(signals.my_callback)

Примечание

Метод ready() можно выполнить более одного раза во время тестировани, таким образом, вам может потребоваться защитить ваши сигналы от дублирования, особенно, если вы планируете отправлять их из тестов.

Сигналы, получаемые от определённых отправителей.

Некоторые сигналы могу быть посланы много раз, но Вам будет нужно получать только определённое подмножество этих сигналов. Например, рассмотрим django.db.models.signals.pre_save - сигнал, посылаемый перед сохранением модели. Бывает, что Вам не нужно знать о сохранении любой модели, Вас интересует только одна конкретная модель:

В этих случаях Вы можете получать только сигналы, посланные определёнными отправителями. В случае django.db.models.signals.pre_save отправитель будет сохраняемой моделью некоторого класса, так что вы можете указать, что вы хотите получать только сигналы, посылаемые этой моделью:

from django.db.models.signals import pre_save
from django.dispatch import receiver
from myapp.models import MyModel


@receiver(pre_save, sender=MyModel)
def my_handler(sender, **kwargs): ...

Функция my_handler будет вызвана только при сохранении объекта класса MyModel.

Отправителями различных сигналов могут быть различные объекты. Для получения детальной информации по каждому такому сигналу обращайтесь к документации по встроенным сигналам.

Предотвращение дублирования сигналов

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

Такое поведение может приводить к проблемам (например, если происходит отправка электронной почты всякий раз, когда посылается сигнал о сохранении модели), поэтому передавайте некоторый уникальный идентификатор в качестве значения аргумента dispatch_uid для идентификации в функции-получателе. Обычно, этот идентификатор является строкой, хотя подойдёт любой хешируемый объект. В итоге функция-получатель будет привязана к сигналу единожды для каждого уникального значения dispatch_uid.

from django.core.signals import request_finished

request_finished.connect(my_callback, dispatch_uid="my_unique_identifier")

Создание и посылка сигналов.

Вы можете создавать свои собственные сигналы в ваших приложениях.

Когда использовать специальные сигналы

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

Создание сигналов

class Signal

Все сигналы являются экземплярами django.dispatch.Signal.

Например:

import django.dispatch

pizza_done = django.dispatch.Signal()

Это объявляет сигнал «pizza_done».

Отправка сигналов

В Django есть два способа синхронной отправки сигналов.

Signal.send(sender, **kwargs)
Signal.send_robust(sender, **kwargs)

Сигналы также могут отправляться асинхронно.

Signal.asend(sender, **kwargs)
Signal.asend_robust(sender, **kwargs)

Чтобы отправить сигнал, вызовите Signal.send(), Signal.send_robust(), await Signal.asend() или await Signal.asend_robust(). Вы должны предоставить аргумент sender (который в большинстве случаев является классом) и можете предоставить столько других ключевых аргументов, сколько захотите.

Например, вот как может выглядеть отправка сигнала pizza_done:

class PizzaStore:
    ...

    def send_pizza(self, toppings, size):
        pizza_done.send(sender=self.__class__, toppings=toppings, size=size)
        ...

Все четыре метода возвращают список пар кортежей [(receiver, response), ...], представляющих список вызванных функций-получателей и их значений ответа.

send() отличается от send_robust() способом обработки исключений, генерируемых функцией-получателем. send() не ловит никаких исключений, сгенерированных в получателе, позволяя исключению проваливаться дальше. Таким образом, не все получатели могут получить сигнал при возникновении ошибки.

send_robust() перехватывает все ошибки, наследуемые от класса Exception языка Python, и гарантирует, что сигнал дойдёт до всех получателей. Если произойдёт ошибка в одном из них, экземпляр исключения будет помещён в кортежную пару, для получателя, который соответствует вызываемой ошибке.

Трассировочная информация доступна через атрибут __traceback__ ошибок, возвращаемых при вызове send_robust().

asend() похож на send(), но это сопрограмма, которую необходимо ожидать:

async def asend_pizza(self, toppings, size):
    await pizza_done.asend(sender=self.__class__, toppings=toppings, size=size)
    ...

Независимо от того, являются ли они синхронными или асинхронными, получатели будут правильно адаптированы к использованию send() или asend(). Синхронные приемники будут вызываться с использованием sync_to_async() при вызове через asend(). Асинхронные получатели будут вызываться с использованием async_to_sync() при вызове через send(). Как и в случае с промежуточным программным обеспечением <async_ Performance>`, такая адаптация приемников требует небольших затрат производительности. Обратите внимание: чтобы уменьшить количество переключателей стиля синхронного/асинхронного вызова в вызове send() или asend(), перед вызовом получатели группируются по тому, являются ли они асинхронными или нет. Это означает, что асинхронный приемник, зарегистрированный до синхронного приемника, может выполняться после синхронного приемника. Кроме того, асинхронные приемники выполняются одновременно с использованием asyncio.gather().

Все встроенные сигналы, за исключением сигналов асинхронного цикла запрос-ответ, отправляются с использованием Signal.send().

Отключение сигнала

Signal.disconnect(receiver=None, sender=None, dispatch_uid=None)

Чтобы отключить приемник от сигнала, вызовите Signal.disconnect(). Аргументы описаны в Signal.connect(). Метод возвращает True, если получатель был отключен, и False, если нет. Когда sender передается как ленивая ссылка на <app label>.<model>, этот метод всегда возвращает None.

В аргументе receiver указывается получатель, который должен перестать получать сигнал. Аргумент может содержать None, если для идентификации получателя используется dispatch_uid.

Back to Top