Сигналы

Django includes a «signal dispatcher» which helps 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.

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

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’s built-in signals let user code get notified of certain actions.

You can also define and send your own custom signals. See Определение и отправка сигналов below.

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

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

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

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

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

Signal.connect(receiver, sender=None, weak=True, dispatch_uid=None)[исходный код]
Параметры:
  • receiver – Функция обратного вызова, которая будет подключена к этому сигналу. Для получения дополнительной информации смотрите Функции приемника.
  • sender – Указывает конкретного отправителя для получения сигналов. См. раздел Подключение к сигналам, посылаемым определенными отправителями для получения дополнительной информации.
  • weak – Django по умолчанию хранит обработчики сигналов как слабые ссылки. Таким образом, если ваш приемник является локальной функцией, он может быть собран в мусор. Чтобы предотвратить это, передайте weak=False при вызове метода сигнала connect().
  • dispatch_uid – Уникальный идентификатор для приемника сигнала в случаях, когда могут быть посланы дублирующие сигналы. См. раздел Предотвращение дублирования сигналов для получения дополнительной информации.

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

Функции приемника

Во-первых, нам нужно определить функцию-приемник. Приемником может быть любая функция или метод 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 выдаст ошибку, если вы так сделаете. Это потому, что в любой момент в сигнал могут быть добавлены аргументы, и ваш приемник должен быть в состоянии обработать эти новые аргументы.

Подключение функций приемника

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

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, чтобы минимизировать побочные эффекты импорта кода.

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(), this will implicitly connect signal handlers:

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() может быть выполнен более одного раза во время тестирования, поэтому вы можете захотеть guard your signals from duplication, особенно если вы планируете отправлять их внутри тестов.

Подключение к сигналам, посылаемым определенными отправителями

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

Различные сигналы используют различные объекты в качестве отправителей; вам нужно обратиться к built-in signal documentation для получения подробной информации о каждом конкретном сигнале.

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

В некоторых обстоятельствах код, соединяющий приемники с сигналами, может выполняться несколько раз. Это может привести к тому, что ваша функция приемника будет зарегистрирована более одного раза и, соответственно, столько же раз вызвана для события сигнала. Например, метод 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.send() (все встроенные сигналы используют это), либо Signal.send_robust(). Вы должны предоставить аргумент sender (который в большинстве случаев является классом) и можете предоставить любое количество других аргументов с ключевыми словами.

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

class PizzaStore:
    ...

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

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

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

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

Трассировки присутствуют на атрибуте __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. When sender is passed as a lazy reference to <app label>.<model>, this method always returns None.

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

Вернуться на верх