信号 signal

阅读: 45303     评论:10

django自带一套信号机制来帮助我们在框架的不同位置之间传递信息。也就是说,当某一事件发生时,信号系统可以允许一个或多个发送者(senders)将通知或信号(signals)发送给一组接受者(receivers)。

信号系统包含以下三要素:

  • 来源者-信号的发出方
  • 信号-信号本身
  • 接收者-信号的接受者

从Django5.0开始支持异步信号。

Django内置了一些信号,下面是一些比较常用的。

位于django.db.models.signals的信号:

  • pre_initpost_init:实例化模型时,在 __init__() 方法执行之前和之后发出

  • pre_savepost_save:在模型实例的save()方法调用之前或之后发送信号

  • pre_deletepost_delete:在模型或查询集的delete()方法调用之前或之后发送信号。
  • m2m_changed:当多对多字段被修改时发送信号。

位于django.core.signals的内置信号:

  • request_startedrequest_finished:当接收和关闭HTTP请求时发送信号。

  • pre_migratepost_migrate:在执行migrate命令前后发送这两个信号。

一、监听信号

要接收信号,请使用Signal.connect()方法注册一个接收器。当信号发送后,会调用这个接收器。

方法原型:

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

参数:

receiver :当前信号连接的回调函数,也就是处理信号的函数。 
sender :监听从哪个发送方发来的信号。 
weak : Django默认将信号处理程序存储为弱引用。因此,如果你的接收器是本地函数,可能会对其进行垃圾回收。要防止这种情况发生,请设置weak=False。
dispatch_uid :信号接收器的唯一标识符,以防信号多次发送。 

下面以如何接收每次HTTP请求结束后发送的信号为例,连接到Django内置的request_finished信号。

1. 编写接收器

接收器其实就是一个Python函数或者方法:

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

请注意,所有的接收器都必须接收一个sender参数和一个**kwargs通配符参数。kwargs会接收所有从信号发生器传递过来的参数值。

接收器也可以是异步函数,如下例子所示:

async def my_callback(sender, **kwargs):
    await asyncio.sleep(5)
    print("Request finished!")

2. 连接信号

其实就是监听信号。有两种方法可以连接信号,一种是下面的手动方式:

from django.core.signals import request_finished

request_finished.connect(my_callback)

另一种是使用receiver()装饰器:

from django.core.signals import request_finished
from django.dispatch import receiver


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

3. 接收特定发送者的信号

一个信号接收器,通常不需要接收所有的信号,只需要接收特定发送者发来的信号,所以需要在sender参数中,指定发送方。下面的例子,只接收MyModel模型的实例保存前的信号。

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实例保存时被调用。

4. 信号的代码位置

严格来说,处理和注册信号的代码可以放在任何你喜欢的地方,但是建议不要放在app的根目录或 models 模块内,以尽量减少导入代码的副作用。

在实践中,信号处理代码通常放在与其相关的app的signals.py模块中,然后在app配置类AppConfigready()方法中连接信号。如果你使用的是receiver()装饰器,请在ready()方法中导入signals子模块,就可以隐式地连接信号,如下面的例子所示:

from django.apps import AppConfig
from django.core.signals import request_finished


class MyAppConfig(AppConfig):
    ...

    def ready(self):

        # 下面两句代码二选一!!!!!

        # 隐式地连接信号处理器
        from . import signals

        # 显示地连接信号处理器
        request_finished.connect(signals.my_callback)

注意: ready() 方法在测试过程中可能会多次执行,因此你可能需要 防止重复信号,尤其是当你计划在测试中发送信号时。

5. 防止重复信号

为了防止重复信号,可以设置dispatch_uid参数来标识你的接收器函数。标识符通常是一个字符串,如下所示:

from django.core.signals import request_finished

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

最后的结果是,对于每个唯一的dispatch_uid值,你的接收器都只绑定到信号一次。

二、自定义信号

除了Django为我们提供的内置信号(比如前面列举的那些),很多时候,我们需要自己定义信号。

注意,这里自定义的是信号,不是信号发送者,也不是信号接收和处理者。

所有的信号都是django.dispatch.Signal的实例。

下面定义了一个新信号:

import django.dispatch

pizza_done = django.dispatch.Signal()

三、发送信号

Django中有两种方法用于发送信号。

  • Signal.send(sender, **kwargs)

或者

  • Signal.send_robust(sender,** kwargs)

必须提供sender参数(大部分情况下是一个类名),并且可以提供任意数量的其他关键字参数。

send()send_robust() 在处理接收器函数所引发异常的方式上有所不同。 send() 捕获接收器引起的任何异常,它只广播异常。

例如,这样来发送前面的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), ... ],表示接收器和响应值二元元组的列表。

除了sendsend_robust,Django从5.0开始提供它们的异步版本asendasend_robust,用法基本一致:

async def asend_pizza(self, toppings, size):
    await pizza_done.asend(sender=self.__class__, toppings=toppings, size=size)
    ...

四、断开信号

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

Signal.disconnect()用来断开信号的接收器,和Signal.connect()中的参数相同。如果接收器成功断开,返回True,否则返回False。

五、信号实例

信号可能不太好理解,下面我在Django内编写一个例子示范一下:

首先在根URLCONF中写一条路由:

from django.urls import path
from django.contrib import admin
from app1 import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('signal/', views.create_signal),
]

这个很好理解,我在项目里创建了一个app1应用,在它的views.py中创建了一个create_signal视图,通过/signal/可以访问这个视图。这些都不重要,随便配置,只要能正常工作就行。

然后在views.py中自定义一个信号,以及创建create_signal视图:

from django.shortcuts import HttpResponse
import time
import django.dispatch
from django.dispatch import receiver

# 定义一个信号
work_done = django.dispatch.Signal()


def create_signal(request):
    url_path = request.path
    print("我已经做完了工作。现在我发送一个信号出去,给那些指定的接收器。")

    # 发送信号,将请求的url地址和时间一并传递过去
    work_done.send(create_signal, path=url_path, time=time.strftime("%Y-%m-%d %H:%M:%S"))
    return HttpResponse("200,ok")

自定义的信号名叫work_done

create_signal视图内,获取请求的url,生成请求的时间,作为参数,传递到send方法。

这样,我们就发送了一个信号。

然后,再写一个接收器:

@receiver(work_done, sender=create_signal)
def my_callback(sender, **kwargs):
    print("我在%s时间收到来自%s的信号,请求url为%s" % (kwargs['time'], sender, kwargs["path"]))

通过装饰器注册为接收器。内部接收字典参数,并解析打印出来。

最终views.py文件如下:

from django.shortcuts import HttpResponse
import time
import django.dispatch
from django.dispatch import receiver

# Create your views here.

# 定义一个信号
work_done = django.dispatch.Signal()


def create_signal(request):
    url_path = request.path
    print("我已经做完了工作。现在我发送一个信号出去,给那些指定的接收器。")

    # 发送信号,将请求的IP地址和时间一并传递过去
    work_done.send(create_signal, path=url_path, time=time.strftime("%Y-%m-%d %H:%M:%S"))
    return HttpResponse("200,ok")


@receiver(work_done, sender=create_signal)
def my_callback(sender, **kwargs):
    print("我在%s时间收到来自%s的信号,请求url为%s" % (kwargs['time'], sender, kwargs["path"]))

现在可以来测试一下。python manage.py runserver启动服务器。浏览器中访问http://127.0.0.1:8000/signal/。可以看到控制台显示的内容:

我已经做完了工作。现在我发送一个信号出去,给那些指定的接收器。
我在2024-6-18 17:10:12时间收到来自<function create_signal at 0x0000000003AFF840>的信号,请求url为/signal/

 网站地图sitemap 序列化 serializers 

评论总数: 10


点击登录后方可评论

信号:1、创建信号对象。2、使用信号对象的send方法在sender函数中携带任意数据发出信号。3、信号监控 4、执行回调



感觉和QT的信号和槽机制一样,先定义一个指定了参数个数和类型的信号,再用一个函数作为槽连接到这个信号,实际就是作为它的回调函数。每当信号被发射,槽函数就是响应它而执行。一个信号可以连接多个槽,多个信号也可以连接在一个槽上,只要参数一致



urlpatterns = [ # url(r'^admin/', admin.site.urls), url(r'^signal/$', views.create_signal), ] 这是1.0版本的写法啊,不是2.0版本吗,应该改成path吧



触发器



老哥,这个综合篇感觉在实际使用中很多功能还是挺有用的,但是没有一个完整的例子(- -尤其是session那一篇)看的一头雾水。。。您有空能不能每个专题加个完整的例子让我跟着抄一下啊。。。谢谢。。。



建议博主有空最好补上完整有用的例子。另外,一定要说清楚综合篇每一个实用工具的真正用途,用于解决什么问题。望采纳,辛苦了。



最佳使用场景 通知类  通知是signal最常用的场景之一。例如,在论坛中,在帖子得到回复时,通知楼主。从技术上来讲,我们可以将通知逻辑放在回复保存时,但是这并不是一个好的处理方式,这样会时程序耦合度增大,不利于系统的后期扩展维护。如果我们在回复保存时,只发一个简单的信号,外部的通知逻辑拿到信号后,再发送通知,这样回复的逻辑和通知的逻辑做到了分开,后期维护扩展都比较容易。 初始化类 信号的另一个列子便是事件完成后,做一系列的初始化工作。 其他一些使用场景总结 以下情况不要使用signal: signal与一个model紧密相关, 并能移到该model的save()时 signal能使用model manager代替时 signal与一个view紧密相关, 并能移到该view中时 以下情况可以使用signal: signal的receiver需要同时修改对多个model时 将多个app的相同signal引到同一receiver中处理时 在某一model保存之后将cache清除时 无法使用其他方法, 但需要一个被调函数来处理某些问题时 如何使用 django 的 signal 使用可分为2个模块: signal :signal定义及触发事件 receiver : signal 接受函数 内建signal的使用 django 内部有些定义好的signal供我们使用: 模型相关: pre_save 对象save前触发 post_save 对象save后触发 pre_delete 对象delete前触发 post_delete 对象delete后触发 m2m_changed ManyToManyField 字段更新后触发 请求相关: request_started 一个request请求前触发 request_finished request请求后触发



博主可以提一下具体的使用场景,初看这个总是想和在create_signal视图中print效果一样,那信号究竟是要解决啥问题?



道出了我的心声。



清晰易懂