Skip to main content

信号

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

Django提供了一个 集内置信号,让用户代码由Django本身通知某些操作。这些包括一些有用的通知:

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

你也可以 define and send your own custom signals;见下文。

听信号

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

我们将看看发件人 a bit later,但现在看看 **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 信号。大多数时候,你不需要知道 any 模型何时保存 - 只需保存一个 specific 模型。

在这些情况下,您可以注册接收特定发件人发送的信号。在 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):
    ...

仅当保存 MyModel 的实例时才会调用 my_handler 函数。

不同的信号使用不同的对象作为其发送者;您需要咨询 内置信号文档 了解每个特定信号的详细信息。

防止重复信号

在一些情况下,将接收器连接到信号的代码可以运行多次。这可能导致您的接收器功能被多次注册,因此对于单个信号事件被称为多次。

如果此行为有问题(例如,当使用信号在保存模型时发送电子邮件),请传递唯一标识符作为 dispatch_uid 参数以标识接收器功能。这个标识符通常是一个字符串,虽然任何hashable对象就足够了。最终结果是,对于每个唯一的 dispatch_uid 值,您的接收器功能将仅绑定到信号一次:

from django.core.signals import request_finished

request_finished.connect(my_callback, dispatch_uid="my_unique_identifier")

定义和发送信号

您的应用程序可以利用信号基础设施并提供自己的信号。

定义信号

class Signal(providing_args=list)[源代码]

所有信号都是 django.dispatch.Signal 实例。 providing_args 是信号将向听众提供的参数名称的列表。这是纯粹的文档化,但是,没有什么检查信号实际上提供这些参数给它的监听器。

例如:

import django.dispatch

pizza_done = django.dispatch.Signal(providing_args=["toppings", "size"])

这声明了一个 pizza_done 信号,它将为接收器提供 toppingssize 参数。

请记住,您可以随时更改此参数列表,因此在第一次尝试时获取API不是必要的。

发送信号

在Django中有两种发送信号的方法。

Signal.send(sender, **kwargs)[源代码]
Signal.send_robust(sender, **kwargs)[源代码]

要发送信号,请调用 Signal.send() (所有内置信号使用此信号)或 Signal.send_robust()。您必须提供 sender 参数(这是大多数时间的类),并可以提供尽可能多的其他关键字参数。

例如,这里是如何发送我们的 pizza_done 信号可能看起来:

class PizzaStore(object):
    ...

    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

1.9 版后已移除: weak 参数已被弃用,因为它没有效果。它将在Django 2.0中删除。