前言:

Django提供一种信号机制。用于在框架执行操作时解耦。通俗来讲,就是一些动作发生的时候,信号允许特定的发送者去提醒一些接受者。 信号机制可以用来帮助我们在框架的不同位置之间传递信息。 当发生一些动作的时候,发出信号,然后监听了这个信号的函数就会执行。
个人理解,Django的signal可理解为Django内部的钩子,当一个事件发生时,其他程序可对其作出相关反应,可通过signal来回调定义好的处理函数(receivers),从而更大程度的解耦我们的系统。

最佳使用场景

通知 是signal最常用的场景之一 ,例如,当用户的金额发生变动时需要通知用户。 从技术上来讲,我们可以将通知逻辑放在更新和保存的时候, 但是这并不是一个好的处理方式,这样会使程序耦合度增大,不利于系统的后期扩展维护。如果我们在保存时,只发一个简单的信号,外部的通知逻辑拿到信号后,再发送通知,这样金额的改变的逻辑和通知的逻辑做到了分开,后期维护扩展都比较容易。

初始化 也可以用到信号, 某个事件完成后,做一系列的初始化工作。

内置信号如何使用

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

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

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

  • django.db.models.signals.pre_init

在ORM模型执行其构造方法前,发送信号

  • django.db.models.signals.post_init

在ORM模型执行其构造方法后,发送信号

  • django.db.models.signals.pre_save

在ORM模型的save()方法调用之前发送信号

  • django.db.models.signals.post_save

在ORM模型的save()方法调用之后发送信号

  • django.db.models.signals.pre_delete

在ORM模型或查询集的delete()方法调用之前发送信号

  • django.db.models.signals.post_delete

在ORM模型或查询集的delete()方法调用之后发送信号

  • django.db.models.signals.m2m_changed

当多对多字段被修改时发送信号

  • django.core.signals.request_started

当接收HTTP请求时发送信号

  • django.core.signals.request_finished

当关闭HTTP请求时发送信号

下面以如何接收金额后发送通知给用户为例,讲解使用Django现成的信号

1. 编写接收器

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

def Change_user_init(instance, **kwargs):
    instance.old_money = instance.money
    
def Changes_user_post(instance, created, **kwargs):
    if not created and instance.money - instance.old_money > 0:
        print('{} 充值了 {} 元'.format(instance.username, instance.money - instance.old_money))
    if not created and instance.money - instance.old_money < 0:
        print('{} 花去了 {} 元'.format(instance.username, instance.old_money - instance.money))

2. 连接接收器

推荐使用@ receiver() 装饰器方式连接:

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

# Django中的model对象执行其构造方法后,自动触发
@receiver(signals.post_init, sender=User)
def Change_user_init(instance, **kwargs):
    # 在模型对象中缓存当前的字段值
    instance.old_money = instance.money

 # Django中的model对象保存后,自动触发
@receiver(signals.post_save, sender=User)
def Changes_user_post(instance, created, **kwargs):
    if not created and instance.money - instance.old_money > 0:
        print('{} 充值了 {} 元'.format(instance.username, instance.money - instance.old_money))
    if not created and instance.money - instance.old_money < 0:
        print('{} 花去了 {} 元'.format(instance.username, instance.old_money - instance.money))

自定义信号如何使用

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

所有的信号都是django.dispatch.Signal的实例。providing_args参数是一个列表,由信号将提供给监听者的参数的名称组成。可以在任何时候修改providing_args参数列表。

下面定义了一个新信号:

from django.dispatch import Signal

# 自定义一个信号
custom_signal = Signal(providing_args=['path', 'time'])

写一个 接收器:

@receiver(custom_signal)
def my_callback(sender, **kwargs):
    path = kwargs['path']
    time = kwargs['time']
    print("我在{}时间收到来自{}的信号,请求url为{}".format(time,sender,path))

连接此接收器:

from utils.signals import custom_signal

class Testsignal2(View):
    def get(self,request):
        url_path = request.path
        print("我已经做完了工作。现在我发送一个信号出去,给那些指定的接收器。")
        custom_signal.send(self.__class__, path=url_path,time=time.strftime("%Y-%m-%d %H:%M:%S"))
        return HttpResponse('ok')

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

现在可以来测试一下。python manage.py runserver启动服务器。浏览器中访问http://127.0.0.1:8000/test2/。重点不再浏览器的返回,而在后台返回的内容:

我已经做完了工作。现在我发送一个信号出去,给那些指定的接收器。
我在2020-03-08 00:44:29时间收到来自<class 'UserManger.views.Testsignal2'>的信号,请求url为/test2/

总结

  • Django signal 的处理是同步的,不适用处理大批量任务。
  • Django signal 对程序的解耦、代码的复用及维护性有很大的帮助。
Last modification:October 7th, 2021 at 11:39 am