Сигналы¶
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
используется для идентификации приемника.