Django中的Signal代码详解


Posted in Python onFebruary 05, 2018

本文研究的主要是Django开发中的signal 的相关内容,具体如下。

前言

在web开发中, 你可能会遇到下面这种场景:

在用户完成某个操作后, 自动去执行一些后续的操作. 譬如用户完成修改密码后,
你要发送一份确认邮件.

当然可以把逻辑写在一起,但是有个问题是,触发操作一般不止一种(如用户更改了其它信息的确认邮件),这时候这个逻辑会需要写多次,所以你可能会想着DRY(Don't repeat yourself),于是你把它写到了一个函数中,每次调用。当然这是没问题的.

但是, 如果你换个思路你会发现另一个完全不同的方案, 即:

  • 类似于daemon的程序监听着特定的事件
  • 前置操作来触发相应的事件
  • 监听程序执行对应的操作

这样的好处是什么呢?

  • 松耦合(不用把后续操作写在主逻辑中)
  • 便于复用(这也是为什么django本身,及第三方应用如pinax大量使用此技术的原因),在各种高级语言中都会有类似的特性,如java,javascript等,而在django中我们使用signal。

观察者模式

Siganl是Django框架中提供的一个 “信号分发器”。它是设计模式中经常提到的观察者模式的一个实现应用。

在此种模式中,一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。

观察者模式的使用场景

  • 关联行为场景,需要注意的是,关联行为是可拆分的,而不是“组合”关系。
  • 事件多级触发场景。
  • 跨系统的消息交换场景,如消息队列、事件总线的处理机制。

优点

1.解除耦合,让耦合的双方都依赖于抽象,从而使得各自的变换都不会影响另一边的变换。

它在被观察者和观察者之间建立一个抽象的耦合。被观察者角色所知道的只是一个具体观察者列表,每一个具体观察者都符合一个抽象观察者的接口。被观察者并不认识任何一个具体观察者,它只知道它们都有一个共同的接口。

由于被观察者和观察者没有紧密地耦合在一起,因此它们可以属于不同的抽象化层次。这种耦合性使得代码的可读性、维护性大大提高。

2.观察者模式实现了动态联动;

由于观察者模式对观察者注册实行管理,那就可以在运行期间,通过动态的控制注册的观察者来控制某个动作的联动范围,从而实现动态联动。

3.观察者模式支持广播通信。

目标发送通知给观察者是面向所有注册的观察者,所以目标每次通知的信息就要对所有注册的观察者进行广播,也可以在目标上添加新的方法来限制广播的范围。

Django 中Siganl 机制的典型应用是,框架为 Models 创建了 pre_save、post_save等与Model的某些方法调用相关联的信号,如pre_save 和 post_save 分别会在 Modle的save()方法的调用之前和之后通知观察者,从而让观察者进行一系列操作。

django signal的处理是同步的,勿用于处理大批量任务。
django signal对程序的解耦、代码的复用及维护性有很大的帮助。

Signal 机制的实现方式

Siganl的源码位于django dispatch包下,主要的代码位于 dispatcher.py中。

在dispatcher中定义了Signal类,以及一个用于使用Python装饰器的方式来连接信号以及信号接受者的方法receiver(signal,**kwargs)。

class Signal(object):
  """
  Base class for all signals

  Internal attributes:

    receivers
      { receiverkey (id) : weakref(receiver) }
  """
  def __init__(self, providing_args=None, use_caching=False):
    """
    创建一个新的Signal
    providing_args 参数,指定这个Siganl 在发出事件(调用send方法)时,可以提供给观察者的信息参数
    比如 post_save()会带上 对应的instance对象,以及update_fields等
    """
    self.receivers = []
    if providing_args is None:
      providing_args = []
    self.providing_args = set(providing_args)
    self.lock = threading.Lock()
    self.use_caching = use_caching
    # For convenience we create empty caches even if they are not used.
    # A note about caching: if use_caching is defined, then for each
    # distinct sender we cache the receivers that sender has in
    # 'sender_receivers_cache'. The cache is cleaned when .connect() or
    # .disconnect() is called and populated on send().
    self.sender_receivers_cache = weakref.WeakKeyDictionary() if use_caching else {}
    self._dead_receivers = False

  def connect(self, receiver, sender=None, weak=True, dispatch_uid=None):

    from django.conf import settings

    if dispatch_uid:
      lookup_key = (dispatch_uid, _make_id(sender))
    else:
      lookup_key = (_make_id(receiver), _make_id(sender))

    if weak:
      ref = weakref.ref
      receiver_object = receiver
      # Check for bound methods
      # 构造弱引用的的receiver
      if hasattr(receiver, '__self__') and hasattr(receiver, '__func__'):
        ref = WeakMethod
        receiver_object = receiver.__self__
      if sys.version_info >= (3, 4):
        receiver = ref(receiver)
        weakref.finalize(receiver_object, self._remove_receiver)
      else:
        receiver = ref(receiver, self._remove_receiver)

    with self.lock:
      #clear掉 由于弱引用 已被垃圾回收期回收的receivers
      self._clear_dead_receivers()
      for r_key, _ in self.receivers:
        if r_key == lookup_key:
          break
      else:
        self.receivers.append((lookup_key, receiver))
      self.sender_receivers_cache.clear()

  def disconnect(self, receiver=None, sender=None, weak=True, dispatch_uid=None):

    if dispatch_uid:
      lookup_key = (dispatch_uid, _make_id(sender))
    else:
      lookup_key = (_make_id(receiver), _make_id(sender))

    disconnected = False
    with self.lock:
      self._clear_dead_receivers()
      for index in range(len(self.receivers)):
        (r_key, _) = self.receivers[index]
        if r_key == lookup_key:
          disconnected = True
          del self.receivers[index]
          break
      self.sender_receivers_cache.clear()
    return disconnected

  def has_listeners(self, sender=None):
    return bool(self._live_receivers(sender))

  def send(self, sender, **named):

    responses = []
    if not self.receivers or self.sender_receivers_cache.get(sender) is NO_RECEIVERS:
      return responses

    for receiver in self._live_receivers(sender):
      response = receiver(signal=self, sender=sender, **named)
      responses.append((receiver, response))
    return responses

  def send_robust(self, sender, **named):

    responses = []
    if not self.receivers or self.sender_receivers_cache.get(sender) is NO_RECEIVERS:
      return responses

    # Call each receiver with whatever arguments it can accept.
    # Return a list of tuple pairs [(receiver, response), ... ].
    for receiver in self._live_receivers(sender):
      try:
        response = receiver(signal=self, sender=sender, **named)
      except Exception as err:
        if not hasattr(err, '__traceback__'):
          err.__traceback__ = sys.exc_info()[2]
        responses.append((receiver, err))
      else:
        responses.append((receiver, response))
    return responses

  def _clear_dead_receivers(self):
    # Note: caller is assumed to hold self.lock.
    if self._dead_receivers:
      self._dead_receivers = False
      new_receivers = []
      for r in self.receivers:
        if isinstance(r[1], weakref.ReferenceType) and r[1]() is None:
          continue
        new_receivers.append(r)
      self.receivers = new_receivers

  def _live_receivers(self, sender):
    """
    过滤掉 已经被 垃圾回收的receiver
    """
    receivers = None
    # 如果使用了cache , 并且没有调用过_remove_receiver 函数 则去 sender_receivers_cache中查找
    if self.use_caching and not self._dead_receivers:
      receivers = self.sender_receivers_cache.get(sender)
      # We could end up here with NO_RECEIVERS even if we do check this case in
      # .send() prior to calling _live_receivers() due to concurrent .send() call.
      if receivers is NO_RECEIVERS:
        return []
    if receivers is None:
      with self.lock:
        self._clear_dead_receivers()
        senderkey = _make_id(sender)
        receivers = []
        for (receiverkey, r_senderkey), receiver in self.receivers:
          if r_senderkey == NONE_ID or r_senderkey == senderkey:
            receivers.append(receiver)
        if self.use_caching:
          if not receivers:
            self.sender_receivers_cache[sender] = NO_RECEIVERS
          else:
            # Note, we must cache the weakref versions.
            self.sender_receivers_cache[sender] = receivers
    non_weak_receivers = []
    for receiver in receivers:
      if isinstance(receiver, weakref.ReferenceType):
        # Dereference the weak reference.
        receiver = receiver()
        if receiver is not None:
          non_weak_receivers.append(receiver)
      else:
        non_weak_receivers.append(receiver)
    return non_weak_receivers

  def _remove_receiver(self, receiver=None):

    self._dead_receivers = True

connect方法

connect方法用于连接信号和信号处理函数,类似的概念相当于为某个事件(信号发出表示一个事件)注册观察者(处理函数),函数参数中receiver就是信号处理函数(函数也是对象,这太方便了),sender表示信号的发送者,比如Django框架中的post_save()这个信号,任何一个模型在save()函数调用之后都会发出这个信号,但是我们只想关注某一个模型 save()方法调用的事件发生,就可以指定sender为我们需要关注的模型类。

weak参数表示是否将receiver转换成弱引用对象,Siganl中默认会将所有的receiver转成弱引用,所以如果你的receiver是个局部对象的话,那么receiver可能会被垃圾回收期回收,receiver也就变成一个dead_receiver了,Siganl会在connect和disconnect方法调用的时候,清除dead_receiver。

dispatch_uid,这个参数用于唯一标识这个receiver函数,主要的作用是防止receiver函数被注册多次,这样会导致receiver函数会执行多次,这可能是我们不想要的一个结果。

disconnect方法

disconnect方法用于断开信号的接收器,函数内首先会生成根据sender和receiver对象构造出的一个标识lookup_key,在遍历receiver数组时,根据lookup_key找到需要disconnect的receiver然后从数组中删除这个receiver。

send和send_robust

send和send_robust方法都是用于发送事件的函数,不同点在于send_robust函数中会捕获信号接收函数发生的异常,添加到返回的responses数组中。

Siganl类的使用

Django signal的处理过程如下图所示:

Django中的Signal代码详解

内建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,参考官方文档:

https://docs.djangoproject.com/en/1.9/topics/signals/

自定义signal的使用

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

第一步,编写signal

myapp.signals.signals.py

importdjango.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中的Signal代码详解的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续参阅本站其他相关专题,如有不足之处,欢迎留言指出。感谢朋友们对本站的支持!

Python 相关文章推荐
在Python中操作字典之setdefault()方法的使用
May 21 Python
python 把数据 json格式输出的实例代码
Oct 31 Python
python实现求最长回文子串长度
Jan 22 Python
python实现人人自动回复、抢沙发功能
Jun 08 Python
python学生管理系统开发
Jan 30 Python
python使用Qt界面以及逻辑实现方法
Jul 10 Python
python tornado使用流生成图片的例子
Nov 18 Python
如何用OpenCV -python3实现视频物体追踪
Dec 04 Python
Python 实现数组相减示例
Dec 27 Python
如何提高python 中for循环的效率
Apr 15 Python
django admin 根据choice字段选择的不同来显示不同的页面方式
May 13 Python
python利用os模块编写文件复制功能——copy()函数用法
Jul 13 Python
Python实现XML文件解析的示例代码
Feb 05 #Python
Python下载网络文本数据到本地内存的四种实现方法示例
Feb 05 #Python
Python实现屏幕截图的两种方式
Feb 05 #Python
Python实现连接两个无规则列表后删除重复元素并升序排序的方法
Feb 05 #Python
用python实现对比两张图片的不同
Feb 05 #Python
使用pygame模块编写贪吃蛇的实例讲解
Feb 05 #Python
Python安装模块的常见问题及解决方法
Feb 05 #Python
You might like
建立动态的WML站点(二)
2006/10/09 PHP
php使用curl发送json格式数据实例
2013/12/17 PHP
PHP实现阳历到农历转换的类实例
2015/03/07 PHP
PHP和C#可共用的可逆加密算法详解
2015/10/26 PHP
php实现scws中文分词搜索的方法
2015/12/25 PHP
Javascript开发之三数组对象实例介绍
2012/11/12 Javascript
让浏览器DOM元素最后加载的js方法
2014/07/29 Javascript
JavaScript使用cookie实现记住账号密码功能
2015/04/27 Javascript
基于jquery实现放大镜效果
2015/08/17 Javascript
jQuery实现的导航动画效果(附demo源码)
2016/04/01 Javascript
js事件源window.event.srcElement兼容性写法(详解)
2016/11/25 Javascript
深入理解 JavaScript 中的 JSON
2017/04/06 Javascript
Angular中使用$watch监听object属性值的变化(详解)
2017/04/24 Javascript
jQuery使用eraser.js插件实现擦除、刮刮卡效果的方法【附eraser.js下载】
2017/04/28 jQuery
详解如何在vue项目中引入elementUI组件
2018/02/11 Javascript
vue2.5.2使用http请求获取静态json数据的实例代码
2018/02/27 Javascript
jQuery实现表格隔行换色
2018/09/01 jQuery
基于Vue 服务端Cookies删除的问题
2018/09/21 Javascript
浅谈针对Vue相同路由不同参数的刷新问题
2018/09/29 Javascript
Node.js爬虫如何获取天气和每日问候详解
2019/08/26 Javascript
react native 仿微信聊天室实例代码
2019/09/17 Javascript
js实现简单选项卡制作
2020/08/05 Javascript
最基础的Python的socket编程入门教程
2015/04/23 Python
Python使用内置json模块解析json格式数据的方法
2017/07/20 Python
python+numpy+matplotalib实现梯度下降法
2018/08/31 Python
python绘制多个子图的实例
2019/07/07 Python
Django 过滤器汇总及自定义过滤器使用详解
2019/07/19 Python
Java byte数组操纵方式代码实例解析
2020/07/22 Python
医药代表个人求职信范本
2013/12/19 职场文书
集体婚礼证婚词
2014/01/13 职场文书
集团公司党的群众路线教育实践活动工作总结
2014/03/03 职场文书
三爱活动实施方案
2014/03/19 职场文书
教师岗位聘任书范文
2014/03/29 职场文书
拉拉队口号
2014/06/16 职场文书
广播体操口号
2014/06/18 职场文书
球队口号
2014/06/18 职场文书