Сигналы
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(). Функция-получатель вызывается при отправке сигнала. Все функции-получатели сигнала вызываются по одной за раз в порядке их регистрации.
-
Signal.connect(receiver, sender=None, weak=True, dispatch_uid=None)[source] -
Параметры: - receiver – Функция обратного вызова, которая будет подключена к этому сигналу. См. Функции-получатели для получения дополнительной информации.
- sender – Указывает конкретного отправителя для получения сигналов. См. Подключение к сигналам, отправленным определёнными отправителями для получения дополнительной информации.
-
weak – Django по умолчанию хранит обработчики сигналов как слабые ссылки. Таким образом, если ваш получатель — локальная функция, она может быть удалена сборщиком мусора. Чтобы предотвратить это, передайте
weak=Falseпри вызове метода сигналаconnect(). - dispatch_uid – Уникальный идентификатор для получателя сигнала в тех случаях, когда могут отправляться дублирующиеся сигналы. См. Предотвращение дублирующихся сигналов для получения дополнительной информации.
Давайте посмотрим, как это работает, зарегистрировав сигнал, который вызывается после завершения каждого HTTP-запроса. Мы будем подключаться к сигналу request_finished.
Функции-получатели
Сначала нам нужно определить функцию-получателя. Получатель может быть любой функцией или методом 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!")
Сигналы могут быть отправлены синхронно или асинхронно, и получатели будут автоматически адаптированы к правильному стилю вызова. См. отправку сигналов для получения дополнительной информации.
Была добавлена поддержка асинхронных получателей.
Подключение функций-получателей
Существует два способа подключения получателя к сигналу. Вы можете использовать ручной способ подключения:
from django.core.signals import request_finished request_finished.connect(my_callback)
В качестве альтернативы можно использовать декоратор receiver():
-
receiver(signal, **kwargs)[source] -
Параметры: - signal – Сигнал или список сигналов, к которым нужно подключить функцию.
- kwargs – Произвольные ключевые аргументы для передачи в функцию.
Вот как вы подключаетесь с помощью декоратора:
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[source]
Все сигналы являются экземплярами django.dispatch.Signal.
Например:
import django.dispatch pizza_done = django.dispatch.Signal()
Это объявляет pizza_done сигнал.
Отправка сигналов
Существует два способа отправки сигналов синхронно в Django.
-
Signal.send(sender, **kwargs)[source]
-
Signal.send_robust(sender, **kwargs)[source]
Сигналы также могут быть отправлены асинхронно.
-
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() перехватывает все ошибки, полученные из класса Python Exception, и гарантирует, что все приёмники будут уведомлены о сигнале. Если произошла ошибка, экземпляр ошибки возвращается в паре кортежей для приёмника, который поднял ошибку.
Обратные трассировки присутствуют в атрибуте __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() при вызове через sync(). Подобно случаю со middleware, существует небольшая стоимость производительности для адаптации приёмников таким образом. Обратите внимание, что для уменьшения числа переключений между синхронными и асинхронными вызовами в вызове send() или asend(), приёмники группируются по тому, являются ли они асинхронными или нет, перед вызовом. Это означает, что асинхронный приёмник, зарегистрированный до синхронного приёмника, может быть выполнен после синхронного приёмника. Кроме того, асинхронные приёмники выполняются параллельно с использованием asyncio.gather().
Все встроенные сигналы, за исключением тех, что находятся в асинхронном цикле запроса-ответа, отправляются с помощью Signal.send().
Была добавлена поддержка асинхронных сигналов.
Отключение сигналов
-
Signal.disconnect(receiver=None, sender=None, dispatch_uid=None)[source]
Чтобы отключить приёмник от сигнала, вызовите Signal.disconnect(). Аргументы описаны в Signal.connect(). Метод возвращает True если приёмник был отключен и False если нет. Когда sender передаётся как леничная ссылка на <app label>.<model>, этот метод всегда возвращает None.
Аргумент receiver указывает зарегистрированный приёмник для отключения. Он может быть None если dispatch_uid используется для идентификации приёмника.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/5.1/topics/signals/