Spec-Zone.ru › Django 4.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 выведет ошибку, если вы это сделаете. Это связано с тем, что в любой момент могут быть добавлены аргументы к сигналу, и ваш получатель должен быть способен обработать эти новые аргументы.

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

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

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.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) [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/4.2/topics/signals/

Spec-Zone.ru

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