Сигналы¶
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 built-in signals позволяет пользовательскому коду получать уведомления об определенных действиях.
Вы также можете определять и посылать собственные пользовательские сигналы. См. Определение и отправка сигналов ниже.
Предупреждение
Сигналы создают видимость свободной связи, но они могут быстро привести к коду, который трудно понять, настроить и отладить.
По возможности следует выбирать прямой вызов кода обработки, а не диспетчеризацию через сигнал.
Прослушивание сигналов¶
Чтобы принять сигнал, зарегистрируйте функцию приемник с помощью метода 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
); все обработчики сигналов должны принимать эти аргументы.
Мы рассмотрим отправители a bit later, но сейчас обратите внимание на аргумент **kwargs
. Все сигналы посылают аргументы с ключевыми словами и могут менять эти аргументы в любое время. В случае с request_finished
он документирован как не посылающий никаких аргументов, что означает, что у нас может возникнуть соблазн написать нашу обработку сигнала как 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
, чтобы минимизировать побочные эффекты импорта кода.
На практике обработчики сигналов обычно определяются в подмодуле signals
приложения, к которому они относятся. Приемники сигналов подключаются в методе ready()
вашего приложения configuration class. Если вы используете декоратор 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()
может быть выполнен более одного раза во время тестирования, поэтому вы можете захотеть 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)[исходный код]¶
Чтобы отключить приемник от сигнала, вызовите команду Signal.disconnect()
. Аргументы описаны в Signal.connect()
. Метод возвращает True
, если приемник был отключен, и False
, если нет. Когда sender
передается как ленивая ссылка на <app label>.<model>
, этот метод всегда возвращает None
.
Аргумент receiver
указывает на зарегистрированный приемник для отключения. Он может быть None
, если dispatch_uid
используется для идентификации приемника.