Spec-Zone.ru › Django 5.2

Сигналы

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(). Аналогично случаю со средниками, адаптация получателей имеет небольшую стоимость производительности. Обратите внимание, что для уменьшения количества переключений между режимами синхронного/асинхронного вызова в 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.2/topics/signals/

Spec-Zone.ru

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