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`
    • 在调用模型的 delete()方法或查询集的delete()方法之前或之后发送。
  • django.db.models.signals.m2m_changed
    • 当模型上的ManyToManyField更改时发送。
  • django.core.signals.request_starteddjango.core.signals.request_finished
    • 在Django启动或完成HTTP请求时发送。

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

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

监听信号

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

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); 所有信号处理程序都必须采用这些参数。

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

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

连接接收器函数

您可以通过两种方式将接收器连接到信号。 您可以采取手动连接路由:

from django.core.signals import request_finished

request_finished.connect(my_callback)

另外,您可以使用receive()装饰器:

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函数。

我的代码该放在哪?

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

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

注解:

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

防止重复信号

在某些情况下,将接收器连接到信号的代码可能会运行多次。这可能会导致您的接收器功能被多次注册,因此对于信号事件将被多次调用。例如,ready()方法可以在测试期间执行多次。更一般而言,这会在项目导入定义信号的模块的任何地方发生,因为信号注册的运行次数与导入的次数相同。

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

from django.core.signals import request_finished

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

定义和发送信号

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

何时使用自定义信号?

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

定义信号

class Signal(providing_args=list)[源代码](https://docs.djangoproject.com/zh-hans/3.0/topics/signals/#django.dispatch.Signal

所有信号都是django.dispatch.Signal实例。provider_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:
    ...

    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。receiver参数指示已注册的接收方断开连接。 如果使用dispatch_uid标识接收者,则可能为None。

posted @ 2020-08-06 18:20  龚政  阅读(372)  评论(0编辑  收藏  举报