信号

Django包括一个“信号调度器”,当框架中的其他地方发生操作时,它可以帮助解耦的应用程序得到通知。简而言之,信号允许某些 senders 通知一组 receivers 已经采取了一些行动。当多段代码可能对相同的事件感兴趣时,它们特别有用。

例如,第三方应用程序可以注册以收到设置更改的通知:

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)

姜戈的 built-in signals 让用户代码收到某些操作的通知。

您还可以定义和发送您自己的自定义信号。看见 定义和发送信号 下面。

警告

信号给人以松散耦合的感觉,但它们可能很快导致难以理解、调整和调试的代码。

如果可能,您应该选择直接调用处理代码,而不是通过信号进行调度。

听信号

要接收信号,请注册A 接受者 函数使用 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 );所有信号处理程序都必须接受这些参数。

我们将查看发送者 a bit later ,但现在看看 **kwargs 争论。所有信号都发送关键字参数,并且可以随时更改这些关键字参数。在.的情况下 request_finished ,它被记录为不发送参数,这意味着我们可能会尝试将信号处理编写为 my_callback(sender)

这是错误的——事实上,如果你这样做,Django会抛出一个错误。这是因为在任何时候,参数都可以添加到信号中,并且您的接收器必须能够处理这些新参数。

接收器也可以是异步函数,具有相同的签名,但使用 async def **

async def my_callback(sender, **kwargs):
    await asyncio.sleep(5)
    print("Request finished!")

信号可以同步或异步发送,接收者将自动适应正确的呼叫风格。看见 sending signals 以获取更多信息。

Changed in Django 5.0:

添加了对异步接收器的支持。

连接接收器功能

有两种方法可以将接收器连接到信号。您可以选择手动连接路径:

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 模块以最小化导入代码的副作用。

实际上,信号处理程序通常在 signals 与之相关的应用程序的子模块。信号接收器连接在 ready() 您的应用程序的 configuration class 。如果您正在使用 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() 方法可能在测试期间执行多次,因此您可能希望 guard your signals from duplication 尤其是如果你打算在测试中发送它们。

连接到特定发送方发送的信号

有些信号会被发送很多次,但您只对接收这些信号的某个子集感兴趣。例如,考虑 django.db.models.signals.pre_save 在保存模型之前发送的信号。大多数时候,你不需要知道什么时候 any 模型被保存——就在一个 具体的 模型被保存。

在这些情况下,您可以注册以接收仅由特定发送者发送的信号。在情况下 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.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()not 捕获接收器引发的任何异常;它只允许传播错误。因此,并非所有的接收器都能在出现错误时收到信号通知。

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() 。类似于 case for middleware ,以这种方式适配接收器的性能代价很小。请注意,为了减少同步/异步呼叫式交换机的数量 send()asend() 呼叫时,根据接收方在被调用之前是否处于异步状态对接收方进行分组。这意味着在同步接收器之前注册的异步接收器可以在同步接收器之后执行。此外,异步接收器还可以使用 asyncio.gather()

所有内置信号(异步请求-响应周期中的信号除外)都使用 Signal.send()

Changed in 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 用于识别接收器。