信号 — Django 文档
信号
Django 包含一个“信号调度器”,它帮助解耦的应用程序在框架中的其他地方发生动作时得到通知。 简而言之,信号允许某些 发送者 通知一组 接收者 某些动作已经发生。 当许多代码段可能对同一事件感兴趣时,它们特别有用。
Django 提供了一组 内置信号 ,可以让 Django 本身通知用户代码某些操作。 其中包括一些有用的通知:
django.db.models.signals.pre_save & django.db.models.signals.post_save
在调用模型的 save() 方法之前或之后发送。
django.db.models.signals.pre_delete & django.db.models.signals.post_delete
django.db.models.signals.m2m_changed
当模型上的 ManyToManyField 更改时发送。
django.core.signals.request_started & django.core.signals.request_finished
在 Django 启动或完成 HTTP 请求时发送。
有关完整列表以及每个信号的完整说明,请参阅 内置信号文档 。
您也可以定义和发送您自己的自定义信号; 见下文。
收听信号
要接收信号,请使用 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
子模块。
连接到特定发件人发送的信号
有些信号会被多次发送,但您只会对接收这些信号的某个子集感兴趣。 例如,考虑在保存模型之前发送的 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
。