Spec-Zone.ru › Django 5.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!")

Сигналы могут быть отправлены синхронно или асинхронно, и получатели автоматически будут адаптированы к соответствующему стилю вызова. См. отправку сигналов для получения дополнительной информации.

Изменено в Django 5.0:

Добавлена поддержка асинхронных получателей.

Подключение функций-получателей

Существует два способа подключения получателя к сигналу. Вы можете выбрать ручное подключение:

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

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

Изменено в Django 5.0:

Добавлена поддержка асинхронных сигналов.

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

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/5.0/topics/signals/

Spec-Zone.ru

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