Django中的Signal代码详解

本文研究的主要是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的处理过程如下图所示:

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

您可能感兴趣的文章:

  • 教你如何在Django 1.6中正确使用 Signal
(0)

相关推荐

  • 教你如何在Django 1.6中正确使用 Signal

    简单回答是: 在其他方法无法使用的情况下, 才最后考虑使用signal. 因为新的django开发人员得知signal之后, 往往会很高兴去使用它. 他们在能使用signal的地方就使用signal, 并且这是他们觉得自己是django专家一样. 然而, 像这样编码一段时间后, django项目就会变得异常复杂, 许多内容都纠结在一起无法解开. 许多开发者也会将django signal和异步消息列队(例如celery)搞混. signal是同步处理, 因此通过signal调用大处理量的进程时并

  • Django中的Signal代码详解

    本文研究的主要是Django开发中的signal 的相关内容,具体如下. 前言 在web开发中, 你可能会遇到下面这种场景: 在用户完成某个操作后, 自动去执行一些后续的操作. 譬如用户完成修改密码后, 你要发送一份确认邮件. 当然可以把逻辑写在一起,但是有个问题是,触发操作一般不止一种(如用户更改了其它信息的确认邮件),这时候这个逻辑会需要写多次,所以你可能会想着DRY(Don't repeat yourself),于是你把它写到了一个函数中,每次调用.当然这是没问题的. 但是, 如果你换个思

  • Django权限机制实现代码详解

    本文研究的主要是Django权限机制的相关内容,具体如下. 1. Django权限机制概述 权限机制能够约束用户行为,控制页面的显示内容,也能使API更加安全和灵活:用好权限机制,能让系统更加强大和健壮.因此,基于Django的开发,理清Django权限机制是非常必要的. 1.1 Django的权限控制 Django用user, group和permission完成了权限机制,这个权限机制是将属于model的某个permission赋予user或group,可以理解为全局的权限,即如果用户A对数

  • 对Django中static(静态)文件详解以及{% static %}标签的使用方法

    在一个网页中,不仅仅只有一个html骨架,还需要css样式文件,js执行文件以及一些图片等.因此在DTL中加载静态文件是一个必须要解决的问题.在DTL中,使用static标签来加载静态文件.要使用static标签,首先需要{% load static %}. 加载静态文件的步骤如下: 首先确保django.contrib.staticfiles已经添加到settings.INSTALLED_APPS中. 确保在settings.py中设置了STATIC_URL. 注意: 上面两条都是在创建Dja

  • django中ImageField的使用详解

    ImageField的使用笔记 今天完善作业写的订单系统,主要是给每一个菜品增加图片,看起来美观一些,但是没想到这个小小的需求花了我一天时间,记录下来,算增长知识了. 使用流程 1.配置setting文件 MEDIA_ROOT代表的是上传图片的根目录,MEDIA_URL代表的是访问文件时url的前缀. # 图片储存根路径 MEDIA_ROOT = join('media') # 图片访问url MEDIA_URL = '/IMG/' 2.model里面增加ImageField属性 up_load

  • django中使用memcached示例详解

    目录 什么是memcached: 安装和启动memcached: windows linux(ubuntu) 启动memcached: telnet操作memcached: 添加数据: 获取数据: 删除数据: 通过python操作memcached: memcached的安全性: 在Django中使用memcached: 什么是memcached: memcached之前是danga的一个项目,最早是为LiveJournal服务的,当初设计师为了加速LiveJournal访问速度而开发的,后来被

  • ES6中的Promise代码详解

    废话不多说了,直接给大家贴代码了,具体如下所示: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> </head> <body> <script> var a=0 var test=function(resolve, reject){ setTimeou

  • Spring中MVC模块代码详解

    SpringMVC的Controller用于处理用户的请求.Controller相当于Struts1里的Action,他们的实现机制.运行原理都类似 Controller是个接口,一般直接继承AbstrcatController,并实现handleRequestInternal方法.handleRequestInternal方法相当于Struts1的execute方法 import org.springframework.web.servlet.ModelAndView; import org.

  • Python中的asyncio代码详解

    asyncio介绍 熟悉c#的同学可能知道,在c#中可以很方便的使用 async 和 await 来实现异步编程,那么在python中应该怎么做呢,其实python也支持异步编程,一般使用 asyncio 这个库,下面介绍下什么是 asyncio : asyncio 是用来编写 并发 代码的库,使用 async/await 语法. asyncio 被用作多个提供高性能 Python 异步框架的基础,包括网络和网站服务,数据库连接库,分布式任务队列等等. asyncio 往往是构建 IO 密集型和

  • Java中可变长度参数代码详解

    到J2SE1.4为止,一直无法在Java程序里定义实参个数可变的方法--因为Java要求实参(Arguments)和形参(Parameters)的数量和类型都必须逐一匹配,而形参的数目是在定义方法时就已经固定下来了.尽管可以通过重载机制,为同一个方法提供带有不同数量的形参的版本,但是这仍然不能达到让实参数量任意变化的目的. 然而,有些方法的语义要求它们必须能接受个数可变的实参--例如著名的main方法,就需要能接受所有的命令行参数为实参,而命令行参数的数目,事先根本无法确定下来. 对于这个问题,

  • 在django中自定义字段Field详解

    Django的Field类中方法有: to_python() # 把数据库数据转成python数据 from_db_value() # 把数据库数据转成python数据 get_pre_value() # 把python数据压缩准备存入数据库 get_db_pre_value() # 把压缩好的数据转成数据库查询集 get_prep_lookup() # 指定过滤的条件 value_to_string() # 数据序列化 如果创建的Field比字符串,日期,整数等更复杂的数据结构,可能需要重写t

随机推荐