信号 — Django 文档

来自菜鸟教程
Django/docs/3.2.x/topics/signals
跳转至:导航、​搜索

信号

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

Django 提供了一组 内置信号 ,可以让 Django 本身通知用户代码某些操作。 其中包括一些有用的通知:

有关完整列表以及每个信号的完整说明,请参阅 内置信号文档

您也可以定义和发送您自己的自定义信号; 见下文。

收听信号

要接收信号,请使用 Signal.connect() 方法注册 receiver 函数。 接收器函数在发送信号时被调用。 一次调用一个信号的所有接收器函数,按照它们注册的顺序。

Signal.connect(receiver, sender=None, weak=True, dispatch_uid=None)
;; 参数
;;* receiver – 将连接到该信号的回调函数。 有关详细信息,请参阅 接收器函数
  • sender – 指定一个特定的发送者来接收信号。 有关详细信息,请参阅 连接到特定发送者发送的信号
  • weak – 默认情况下,Django 将信号处理程序存储为弱引用。 因此,如果您的接收器是一个本地函数,它可能会被垃圾收集。 为防止这种情况,请在调用信号的 connect() 方法时传递 weak=False
  • dispatch_uid – 在可能发送重复信号的情况下,信号接收器的唯一标识符。 有关详细信息,请参阅 防止重复信号

让我们通过注册一个在每个 HTTP 请求完成后调用的信号来看看这是如何工作的。 我们将连接到 request_finished 信号。

接收器功能

首先,我们需要定义一个接收器函数。 接收器可以是任何 Python 函数或方法:

def my_callback(sender, **kwargs):
    print("Request finished!")

请注意,该函数采用 sender 参数以及通配符关键字参数 (**kwargs); 所有信号处理程序都必须采用这些参数。

我们稍后会查看发送方 [[#connecting-to-specific-signals|]] ,但现在查看 **kwargs 参数。 所有信号都发送关键字参数,并且可以随时更改这些关键字参数。 在 request_finished 的情况下,它被记录为不发送参数,这意味着我们可能会尝试将我们的信号处理写为 my_callback(sender)

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


连接接收器功能

有两种方法可以将接收器连接到信号。 您可以采用手动连接路线:

from django.core.signals import request_finished

request_finished.connect(my_callback)

或者,您可以使用 receiver() 装饰器:

receiver(signal)
;; 参数
signal – 连接函数的信号或信号列表。

以下是您与装饰器连接的方式:

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() 装饰器,请在 ready() 中导入 signals 子模块。


笔记

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.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() 是否 not 捕获接收者引发的任何异常; 它只是允许错误传播。 因此,并非所有接收器都可以在遇到错误时收到信号通知。

send_robust() 捕获源自 Python 的 Exception 类的所有错误,并确保所有接收器都收到信号通知。 如果发生错误,则错误实例在引发错误的接收者的元组对中返回。

回溯出现在调用 send_robust() 时返回的错误的 __traceback__ 属性上。


断开信号

Signal.disconnect(receiver=None, sender=None, dispatch_uid=None)

要断开接收器与信号的连接,请调用 Signal.disconnect()。 参数如 Signal.connect() 中所述。 如果接收器断开连接,该方法返回 True,否则返回 False

receiver 参数表示已注册的接收器要断开连接。 如果使用dispatch_uid来识别接收者,则可能是None