Сигналы

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