这不是大神的随笔,只是记忆力不好的码农笔记
参考:https://segmentfault.com/a/1190000008455657
参考:https://blog.csdn.net/laughing2333/article/details/53159109
什么是django的signal
官方文档描述如下:
Django includes a “signal dispatcher” which helps allow decoupled applications get notified when actions occur elsewhere in the framework.In a nutshell, signals allow certain senders to notify a set of receivers that some action has taken place. They’re especially useful when many pieces of code may be interested in the same events.
Django内部包含了一位“信号调度员”:当某事件在框架内发生时,它可以通知到我们的应用程序。 简而言之,当event(事件)发生时,signals(信号)允许若干 senders(寄件人)通知一组 receivers(接收者)。这在我们多个独立的应用代码对同一事件的发生都感兴趣时,特别有用。
最佳使用场景
通知类
通知是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供我们使用
模型相关:
- prec_save 对象save前触发
- post_save 对象save后触发
- pre_delete 对象delete前触发
- post_delete 对象delete后触发
- m2m_changed ManyToManyField 字段更新后触发
请求相关:
- request_started 一个request请求前触发
- request_finished request 请求后触发
针对django自带的signal,我们只需要编写receiver即可,使用如下。
如果想要一个函数或者一个实例方法订阅一个信号,Django Signals提供了两种方法
- 使用receiver装饰器
- 使用signal实例的connect方法
-
编写receiver并绑定到signal
myapp/signals/handlers.pyfrom django.dispatch import receiver from django.core.signals import request_finished ## 方式一绑定方式 @receiver(request_finished, dispatch_uid="request_finished") def my_signal_handler(sender, **kwargs): print("Request finished!================================")sender # 方式二绑定方式 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参数来标记一个函数或者实例方法,dispatch_uid确保此receiver只调用一次
-
第二步,加载signal
myapp/__init__pydefault_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,参考官方文档
https://docs.djangoproject.com/en/1.9/topics/signals/
自定义signal的使用
自定义signal,需要我们编写signal和receiver
一、使用receiver装饰器
- 编写signal
myapp.signals.signals.py
在Signal实例化中的providing_args声明了订阅这个signal的recevier会接收到哪些关键字参数,但是Django Signals并不会对这个参数是否准确进行检查,也就是说即使在调用send方法的时候如果传入了一个没有在providing_args中定义的关键字,Django也不会报错import django.dispatch my_signal = django.dispatch.Signal(providing_args=["my_signal_arg1", "my_signal_arg2"])
- 加载signal
settings中注册app
由于在django.setup()的过程中,它会遍历settings.INSTALLED_APPS列表中的每一项,并调用该AppConfig的ready方法,因此,将recevier订阅signal的过程放置于ready方法中就能保证该代码的执行INSTALLED_APPS = [ 'myapp.apps.MyappConfig' ]
myapp/apps.pyfrom 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
sender必须有,默认为None,后面的参数随便传,signal不会检验from .signals.signals import my_signal def send_sign(request): my_signal.send(sender="some function or class", my_signal_arg3="something", my_signal_arg4="something else")
- 收到signal,执行receiver
myapp/signals/handlers.pyfrom 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')
二、使用connect来订阅信号
- 编写signal
myapp.signals.signals.py
在Signal实例化中的providing_args声明了订阅这个signal的recevier会接收到哪些关键字参数,但是Django Signals并不会对这个参数是否准确进行检查,也就是说即使在调用send方法的时候如果传入了一个没有在providing_args中定义的关键字,Django也不会报错from django.dispatch import Signal my_signal = Signal(providing_args=[])
- 加载signal
settings中注册app
由于在django.setup()的过程中,它会遍历settings.INSTALLED_APPS列表中的每一项,并调用该AppConfig的ready方法,因此,将recevier订阅signal的过程放置于ready方法中就能保证该代码的执行INSTALLED_APPS = [ 'myapp.apps.MyappConfig' ]
myapp/apps.pyfrom django.apps import AppConfig from myapp.signals.signals import my_signal from myapp.signals.handlers import my_signal_handler class MyappConfig(AppConfig): name = 'myapp' def ready(self): my_signal.connect(my_signal_handler, dispatch_uid='my_sign_receiver')
- 事件触发时,发送signal
myapp/views.pyfrom django.http import HttpResponse from myapp.signals.signals import my_signal def send_sign(request): my_signal.send(__name__) return HttpResponse("ok")
- 收到signal,执行receiver
myapp/signals/handlers.pydef my_signal_handler(sender, **kwargs): print("hello world")
总结
django signal的处理是同步的,勿用于处理大批量任务。
django signal对程序的解耦、代码的复用及维护性有很大的帮助