信号
Django有一个“信号调度器(signal dispatcher)”,用来帮助解耦的应用获知框架内任何其他地方发生了操作。简单地说,信号允许某些 发送器 去通知一组 接收器 某些操作发生了。当许多代码段都可能对同一事件感兴趣时,信号特别有用。
例如,第三方应用程序可以注册以接收设置更改的通知:
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)[源代码]
参数: |
|
---|
让我们通过注册一个在每个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)[源代码]
参数: |
|
---|
以下是你如何使用装饰器连接:
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
子模块,这将隐式连接信号处理程序:
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
[源代码]
所有的信号都是 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()
不 捕获接收器引起的任何异常;它只是允许错误传播。因此,并非所有的接收器都会在出现错误时被通知信号。
send_robust()
捕获从 Python的 Exception
类派生的所有错误,并确保所有接收器都收到信号通知。如果发生错误,将在引发错误的接收器的元组对中返回错误实例。
回溯出现在调用 send_robust()
时返回的错误中的 __traceback__
属性中。
断开信号
Signal.disconnect
(receiver=None, sender=None, dispatch_uid=None)[源代码]
要从信号中断开接收者,调用 Signal.disconnect()。参数与 Signal.connect() 中描述的一样。如果成功断开接收者,该方法返回 True
,否则返回 False
。当将 sender
作为对 <app label>.<model>
的延迟引用传递时,该方法始终返回 None
。
receiver
参数表明要断开的接收器。它可以是 None
如果 dispatch_uid
已经被用来标识接收器。