Django3.0 信号


1. 简介

信号

很明显,翻译的时候确实没有中文的。

Django包含一个“信号分配器”,当在框架中其他位置发生操作时,该信号分配器可帮助通知已分离的应用程序。

简而言之,信号使某些发送者可以通知一组接收者已经采取了某些措施。

当许多代码片段可能对同一事件感兴趣时,它们特别有用。

Django提供了一组内置信号,这些信号使Django本身可以将某些操作通知给用户代码。

其中包括一些有用的通知:

  • django.db.models.signals.pre_save:调用模型save()方法之前
  • django.db.models.signals.post_save:调用模型save()方法之后
  • django.db.models.signals.pre_delete:调用模型的delete()方法或查询集的delete()方法之前
  • django.db.models.signals.post_delete:调用模型的delete()方法或查询集的delete()方法之后
  • django.db.models.signals.m2m_changed:更改模型上的ManyToManyField时
  • django.core.signals.request_started:收到HTTP请求时
  • django.core.signals.request_finished:完成HTTP请求时

请参阅内置信号文档以获取完整列表以及每个信号的完整说明。

您还可以定义和发送自己的自定义信号。

2. 监听信号

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

Signal.connect(receiver, sender=None, weak=True, dispatch_uid=None)

参数:

  • receiver:将与该信号连接的回调函数。有关更多信息,请参见接收器功能
  • sender:指定要从中接收信号的特定发送者。有关更多信息,请参见连接到特定发件人发送的信号
  • weak:Django默认将信号处理程序存储为弱引用。因此,如果您的接收器是本地函数,则可能会对其进行垃圾回收。为避免这种情况,请在调用信号的connect()方法时传递weak = False。
  • dispatch_uid:在可能发送重复信号的情况下,信号接收器的唯一标识符。有关更多信息,请参见防止重复信号

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

2.1. 接收方法

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

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

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

我们稍后再看发送者,但现在看一下**kwargs参数。
所有信号都发送关键字自变量,并且可以随时更改这些关键字自变量。
在request_finished的情况下,它被记录为不发送任何参数,这意味着我们可能很想将信号处理编写为my_callback(sender)。

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

2.2. 连接接收方法

您可以通过两种方式将接收器连接到信号。

  • 手动连接路线:
    from django.core.signals import request_finished
    
    request_finished.connect(my_callback)
    
  • 使用receiver(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函数。

我的代码该放在哪?
严格来说,信号处理和注册代码可以存在于您喜欢的任何位置,尽管建议避免使用应用程序的根模块及其模型模块,以最大程度地减少导入代码的副作用。

实际上,信号处理程序通常在它们所涉及的应用程序的信号子模块中定义。 信号接收器连接在应用程序配置类的ready()方法中。 如果您使用的是receiver()装饰器,则将信号子模块导入ready()中。

注解:
在测试过程中,ready()方法可能会执行多次,因此,您可能希望防止信号重复,特别是如果您打算在测试中发送它们时。

2.3. 连接到特定发送者发送的信号

有些信号会发送多次,但是您只会对接收这些信号的某个子集感兴趣。

例如,考虑在保存模型之前发送的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函数。

不同的信号使用不同的对象作为其发送者。
您需要查阅内置信号文档以获取每个特定信号的详细信息。

2.4. 防止重复信号

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

如果此行为有问题(例如,在保存模型时使用信号发送电子邮件),请传递唯一的标识符作为dispatch_uid参数来标识您的接收器功能。
该标识符通常是一个字符串,尽管任何可哈希对象都足够。
最终结果是,对于每个唯一的dispatch_uid值,您的接收器函数将仅与信号绑定一次:

from django.core.signals import request_finished

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

3. 定义和发送信号

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

何时使用自定义信号

信号是隐式函数调用,这会使调试更加困难。
如果自定义信号的发送者和接收者都在项目中,那么最好使用显式函数调用。

3.1. 定义信号

class Signal(providing_args=list)

所有信号都是django.dispatch.Signal实例。
provider_args是信号将提供给侦听器的参数名称的列表。
但是,这纯粹是文档性的,因为没有什么可以检查信号是否确实将这些参数提供给其侦听器的。

例如:

import django.dispatch

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

这声明了pizza_done信号,该信号将为接收者提供 toppings 和 size 参数。

请记住,您可以随时更改此参数列表,因此无需在第一次尝试时就正确设置API。

3.2. 发送信号

在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__属性上提供了追溯。

4. 断开信号

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

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

接收方参数指示已注册的接收方断开连接。
如果使用dispatch_uid标识接收者,则可能为None。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值