Django signal的使用

什么是django的signal

Django内部包含了一位“信号调度员”:当某事件在框架内发生时,它可以通知到我们的应用程序。 简而言之,当event(事件)发生时,signals(信号)允许若干 senders(寄件人)通知一组 receivers(接收者)。这在我们多个独立的应用代码对同一事件的发生都感兴趣时,特别有用。

个人理解,django的signal可理解为django内部的钩子,当一个事件发生时,其他程序可对其作出相关反应,可通过signal来回调定义好的处理函数(receivers),从而更大程度的解耦我们的系统。

使用signals进行工作

Django 自带了一个信号调度程序允许receiver函数在某个动作出现时候去获取通知。当你需要你的代码执行某些时间的同时发生些其他时间的时候,信号非常有用。 我们还可以创建一个自定义的信号,让别人在某个事件发生的时候可以获得通知。
Django自带的models提供了几个信号,它们位于django.db.models.signals。举几个常见例子:

  • pre_save:调用model的save()方法前发送信号
  • post_save:调用model的save()方法后发送信号
  • pre_delete:调用model活着QuerySets的delete()方法前发送信号
  • post_delete:同理,调用delete()后发送信号
  • m2m_changed:当一个模型上的ManyToManyField字段被改变的时候发送信号

最佳使用场景

通知类 

通知是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请求后触发

针对django自带的signal,我们只需要编写receiver 即可,使用如下。

第一步,编写receiver并绑定到signal

myapp/signals/handlers.py

from django.dispatch import receiver
from django.core.signals import request_finished
 
## decorators 方式绑定
@receiver(request_finished, dispatch_uid="request_finished")
def my_signal_handler(sender, **kwargs):
    print("Request finished!================================")

# 普通绑定方式
def my_signal_handler(sender, **kwargs):
    print("Request finished!================================")

request_finished.connect(my_signal_handler)

#####################################################
# 针对model 的signal 
from django.dispatch import receiver
from django.db.models.signals import post_save
 
from polls.models import MyModel
 
 
@receiver(post_save, sender=MyModel, dispatch_uid="mymodel_post_save")
def my_model_handler(sender, **kwargs):
 print('Saved: {}'.format(kwargs['instance'].__dict__))
  • dispatch_uid 确保此receiver 只调用一次

第二步,加载signal

myapp/__init__py

default_app_config = 'myapp.apps.MySendingAppConfig'

myapp/apps.py

from django.apps import AppConfig
 
 
class MyAppConfig(AppConfig):
    name = 'myapp'
 
    def ready(self):
        # signals are imported, so that they are defined and can be used
        import myapp.signals.handlers

到此,当系统受到request 请求完成后,便会执行receiver。

自定义signal的使用

自定义signal,需要我们编写 signal和 receiver 。

第一步, 编写signal

myapp.signals.signals.py

import django.dispatch
 
my_signal = django.dispatch.Signal(providing_args=["my_signal_arg1", "my_signal_arg_2"])

第二步,加载signal

myapp/__init__py

    
default_app_config = 'myapp.apps.MySendingAppConfig'

myapp/apps.py

from django.apps import AppConfig
 
 
class MyAppConfig(AppConfig):
    name = 'myapp'
 
    def ready(self):
        # signals are imported, so that they are defined and can be used
        import myapp.signals.handlers

第三步,事件触发时,发送signal

myapp/views.py

from .signals.signals import my_signal
 
my_signal.send(sender="some function or class",
               my_signal_arg1="something", my_signal_arg_2="something else"])

自定义的signal,django已经为我们编写了此处的事件监听。

第四步,收到signal,执行receiver

myapp/signals/handlers.py

from django.dispatch import receiver
from myapp.signals.signals import my_signal
 
 
@receiver(my_signal, dispatch_uid="my_signal_receiver")
def my_signal_handler(sender, **kwargs):
    print('my_signal received')

此时,我们自定义的signal 便开发完成了。

为此,django提供了很多内置的信号,比如一些常用的功能(以几个在django.db.models.signal目录下的信号为例):

  • save:pre_save和post_save
  • delete:pre_delete和post_delete
  • change:m2m_changed

django包含了一个“信号分配器”使得当一些动作在框架的其他地方发生的时候,解耦的应用可以得到提醒。通俗来讲,就是一些动作发生的时候,信号允许特定的发送者去提醒一些接受者,这是特别有用的设计因为有些代码对某些事件是特别感兴趣的,比如删除动作。

为此,django提供了很多内置的信号,比如一些常用的功能(以几个在django.db.models.signal目录下的信号为例):

  • save:pre_save和post_save
  • delete:pre_delete和post_delete
  • change:m2m_changed

监听信号

要想接受信号,你首先要注册一个接收器函数,当信号被Signal.connect()方法发射的时候,这个函数会被调用

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

参数解释:

  • receiver:连接到这个信号的回调函数
  • sender:信号的发送者
  • weak:是否是弱引用,默认是真。因此,如果你的接收器是是一个本地函数,会被当做垃圾回收,如果你不想,请在使用connect()方法的时候使用weak=False
  • dispatch_uid:一个唯一的标识符给信号接收器,避免重复的信号被发送

下面让我们来看一个具体的例子来解释这些参数吧吧,这个例子以request_finished信号(每个HTTP请求结束的时候会被调用):

回调函数receiver

回调函数可以是一个函数或者方法,比如我们可以这样定义一个接收器:

  1. def my_callback(sender, **kwargs):

  2. print "Request finished!"

注意的是所有的信号处理器都需要这两个参数:sender和**kwargs。因为所有的信号都是发送关键字参数的,可能你处理的时候没有任何参数,但不意味着在处理的过程中(在你写的处理函数之前)有任何的参数生成,如果没有传**kwargs参数的话,可能会发生问题;基于这样的考虑,这两个参数都是必须的。

连接到你的receiver回调函数

有两种方法可以把信号和接收器连接到一起:

connect方法

  1. from django.core.signals import request_finished

  2.  
  3. request_finished.connect(my_callback)

装饰器方法

  1. from django.core.signals import request_finished

  2. from django.dispatch import receiver

  3.  
  4. @receiver(request_finished)

  5. def my_callback(sender, **kwargs):

  6. print "Request finished!"

这样配置之后,每次HTTP接受的时候都会调用这个接收器回调函数了

绑定特定的发送者

记上面之后,你会不会想到这样的一个问题:每次都调用,会不会很烦啊?如果是我的话,我肯定觉得很烦,毕竟我不是想接受所有人的信号的,所以你需要设置sender关键字参数

第二个知识:每一类的信号都对应着特定的发送者,所以要绑定发送者也得绑定对应的发送者类型,例如,request_finished对应的是handler class,而pre_save对应则是model class

下面我们以pre-save为例子绑定特定的发送者(模型):

  1. from django.db.models.signals import pre_save

  2. from django.dispatch import receiver

  3. from myapp.models import MyModel

  4.  
  5. @receiver(pre_save, sender=MyModel)

  6. def my_handler(sender, **kwargs):

预防重复的信号

使用dispatch_uid关键字参数

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

说完了如何监听一个信号,下面我们继续讲解定义和发送信号吧】

定义信号

class Signal([providing_args=list])

所有的信号都是django.dispatch.Signal的实例,参数providing_args是一个信号提供给监听器的参数名的列表,比如:

import django.dispatch

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

这段代码定义了一个pizza_done的信号,参数有toppings和size

发送信号

有两个方法发送信号

Signal.send(sender,**kwargs)

Signal.send_robust(sender,**kwargs)

sender参数是必须的,关键字参数可选

  1. class PizzaStore(object):

  2. ...

  3.  
  4. def send_pizza(self, toppings, size):

  5. pizza_done.send(sender=self, toppings=toppings, size=size)

这两种方法都返回一个 元组对[(receiver,respose),...] 的 列表 ,一个代表被调用的receiver回调函数和他们的response的列表

这两种方法的区别在于send不会捕捉任何的异常,(放任错误的传播),而send_robust则是捕捉所有的异常,并确保每个接收器都知道这个信号(发生错误了)(如果发生错误的话,错误实体和发生错误的接收器作为一个元组对一起返回给那个列表

断开信号

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

和监听信号类似

receiver参数用来指明那个接收器被断开,如果使用了dispatch_uid的话,receiver可以为None

总结

  • django signal 的处理是同步的,勿用于处理大批量任务。

  • django signal 对程序的解耦、代码的复用及维护性有很大的帮助。

猜你喜欢

转载自blog.csdn.net/hzlnice/article/details/81436289
今日推荐