Spec-Zone.ru › Django 6.0

Сигналы

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) [исходный код]
Параметры:
  • 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) [исходный код]
Параметры:
  • 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 [исходный код]

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

Например:

import django.dispatch

pizza_done = django.dispatch.Signal()

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

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

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

Signal.send(sender, **kwargs) [исходный код]
Signal.send_robust(sender, **kwargs) [исходный код]

Сигналы также можно отправлять асинхронно.

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

Трассировки стека доступны в атрибуте __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(). Синхронные получатели при вызове через asend() вызываются с помощью sync_to_async(). Асинхронные получатели при вызове через send() вызываются с помощью async_to_sync(). Как и в случае с промежуточным ПО, такая адаптация получателей сопряжена с небольшими затратами производительности. Обратите внимание: чтобы уменьшить количество переключений между синхронным и асинхронным стилями вызова внутри вызова send() или asend(), получатели перед вызовом группируются в зависимости от того, являются ли они асинхронными. Это означает, что асинхронный получатель, зарегистрированный перед синхронным, может выполниться после синхронного. Кроме того, асинхронные получатели выполняются конкурентно с помощью asyncio.gather().

Все встроенные сигналы, кроме сигналов в асинхронном цикле запрос-ответ, отправляются с помощью Signal.send().

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

Signal.disconnect(receiver=None, sender=None, dispatch_uid=None) [исходный код]

Чтобы отключить получателя от сигнала, вызовите 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/6.0/topics/signals/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API