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

Сигналы

Django includes a «signal dispatcher» which helps allow decoupled applications get notified when actions occur elsewhere in the framework. In a nutshell, signals allow certain senders to notify a set of receivers that some action has taken place. They’re especially useful when many pieces of code may be interested in the same events.

Django provides a set of built-in signals that let user code get notified by Django itself of certain actions. These include some useful notifications:

See the built-in signal documentation for a complete list, and a complete explanation of each signal.

You can also define and send your own custom signals; see below.

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

Для того, чтобы принять сигнал, Вам необходимо с помощью метода 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) в формате словаря; все обработчики сигналов должны принимать подобные аргументы.

We’ll look at senders a bit later, but right now look at the **kwargs argument. All signals send keyword arguments, and may change those keyword arguments at any time. In the case of request_finished, it’s documented as sending no arguments, which means we might be tempted to write our signal handling as my_callback(sender).

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

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

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

from django.core.signals import request_finished

request_finished.connect(my_callback)

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

receiver(signal)
Параметры:

signal – Сигнал или список обрабатываемых сигналов.

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

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, чтобы сократить побочный эффект при импорте приложения.

In practice, signal handlers are usually defined in a signals submodule of the application they relate to. Signal receivers are connected in the ready() method of your application configuration class. If you’re using the receiver() decorator, import the signals submodule inside ready().

Примечание

Метод 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».

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

There are two ways to send signals in Django.

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

To send a signal, call either Signal.send() (all built-in signals use this) or Signal.send_robust(). You must provide the sender argument (which is a class most of the time) and may provide as many other keyword arguments as you like.

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

class PizzaStore:
    ...

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

Both send() and send_robust() return a list of tuple pairs [(receiver, response), ... ], representing the list of called receiver functions and their response values.

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

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

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

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

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

To disconnect a receiver from a signal, call Signal.disconnect(). The arguments are as described in Signal.connect(). The method returns True if a receiver was disconnected and False if not.

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

Back to Top