Сигналы

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

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

Полный список и полное объяснение каждого сигнала см. Во встроенной документации по сигналам.

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

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

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

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

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

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

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

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

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

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

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

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

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

from django.core.signals import request_finished

request_finished.connect(my_callback)

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

receiver( сигнал )
Параметры: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модуля, чтобы минимизировать побочные эффекты импорта кода.

На практике обработчики сигналов обычно определяются в signals подмодуле приложения, к которому они относятся. Приемники сигналов подключаются в ready()методе класса конфигурации вашего приложения. Если вы используете receiver() декоратор, импортируйте signalsподмодуль внутрь 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")

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

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

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

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

Определение сигналов

класс Signal

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

Например:

import django.dispatch

pizza_done = django.dispatch.Signal()

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

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

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

Signal.send( отправитель , ** kwargs )
Signal.send_robust( отправитель , ** 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()улавливает все ошибки, производные от Exceptionкласса Python , и гарантирует, что все получатели уведомлены о сигнале. Если возникает ошибка, экземпляр ошибки возвращается в паре кортежей для получателя, вызвавшего ошибку.

Обратные трассировки присутствуют в __traceback__атрибуте ошибок, возвращаемых при вызове send_robust().

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

Signal.disconnect( получатель = Нет , отправитель = Нет , dispatch_uid = Нет )

Чтобы отключить приемник от сигнала, позвоните Signal.disconnect(). Аргументы описаны в Signal.connect(). Метод возвращает, Trueесли приемник был отключен, а Falseесли нет.

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

Copyright ©2021 All rights reserved