深入理解Django-Signals信号量


Posted in Python onFebruary 19, 2019

定义Signals

Django自身提供了一些常见的signal,用户本身也可以定义自己需要的signal

定义signal很简单,只需要实例化一个Signal实例即可

实例化Signal时,可以传入关键词参数providing_args, providing_args是一个列表,列表中定义了当前signal调用send方法时可以传入的参数。

# django.core.signals.py

from django.dispatch import Signal

request_started = Signal(providing_args=["environ"])
request_finished = Signal()
got_request_exception = Signal(providing_args=["request"])
setting_changed = Signal(providing_args=["setting", "value", "enter"])

其中Signal的初始化也比较简单,就是为实例化的signal定义一个线程锁

class Signal(object):
  def __init__(self, providing_args=None, use_caching=False):
    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

定义Signal处理函数

Signal处理函数是一个函数或者是一个实例的方法,并且必须满足下面条件:

  1. hashable
  2. 可以接收关键词参数

其中处理函数必须包含的关键词参数有两个:

  • signal,要接收的Signal实例
  • sender,要接收的Signal触发者
# django.db.__init__.py

from django.core import signals
from django.db.utils import ConnectionHandler

connections = ConnectionHandler()


def reset_queries(**kwargs):
  for conn in connections.all():
    conn.queries_log.clear()
signals.request_started.connect(reset_queries)


def close_old_connections(**kwargs):
  for conn in connections.all():
    conn.close_if_unusable_or_obsolete()
signals.request_started.connect(close_old_connections)
signals.request_finished.connect(close_old_connections)

处理函数绑定Signal

django提供了两种方法可以将Signal的处理函数和Signal实例进行绑定:

  • 手动调用connect方法
  • 使用装饰器receiver

其实装饰器receiver最终还是调用了connect方法将处理函数和Signal实例进行绑定

Signal类的connect方法定义如下:

class Signal(object):

  ...
  
  def connect(self, receiver, sender=None, weak=True, dispatch_uid=None):
    from django.conf import settings

    # If DEBUG is on, check that we got a good receiver
    if settings.configured and settings.DEBUG:
      assert callable(receiver), "Signal receivers must be callable."

      # Check for **kwargs
      if not func_accepts_kwargs(receiver):
        raise ValueError("Signal receivers must accept keyword arguments (**kwargs).")

    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
      if hasattr(receiver, '__self__') and hasattr(receiver, '__func__'):
        ref = WeakMethod
        receiver_object = receiver.__self__
      if six.PY3:
        receiver = ref(receiver)
        weakref.finalize(receiver_object, self._remove_receiver)
      else:
        receiver = ref(receiver, self._remove_receiver)

    with self.lock:
      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()

每个信号量根据receiver和sender都可以获取一个lookup_key可以唯一的标志一个Signal和其处理方法, 当调用Signal实例的connect方法时,会判断绑定的处理函数是否已经在自身receivers中,如果存在则不会重复注册

发送Singal

有了前面定义的Signal实例,以及定义的Signal实例处理方法,经过处理函数绑定Signal实例后就可以在必要的地方发送信号, 然后让绑定的处理函数处理了。

# django.core.handlers.wsgi.py

from threading import Lock

from django.core import signals
from django.core.handlers import base

 

class WSGIHandler(base.BaseHandler):

  ...

  def __call__(self, environ, start_response):

    ...
    
    signals.request_started.send(sender=self.__class__, environ=environ)
    
    ...

信号量最为Django的一个核心知识点,在项目中很少有使用到,所以很多人都不了解或者没听过过(包括我)。简单来说就是在进行一些操作的前后我们可以发出一个信号来获得特定的操作,这些操作包括

django.db.models.signals.pre_save&django.db.models.signals.post_save
在模型 save()方法调用之前或之后发送。

django.db.models.signals.pre_delete&django.db.models.signals.post_delete
在模型delete()方法或查询集的delete() 方法调用之前或之后发送。

django.db.models.signals.m2m_changed
模型上的 ManyToManyField 修改时发送。

django.core.signals.request_started&django.core.signals.request_finished
Django开始或完成HTTP请求时发送。

其他细致的知识点,大家可以点链接查看,直接通过一个例子解释:

在自定义用户模型类的时候,在后台添加用户数据因为使用了自定义模型类的create所以密码会以明文保存,接下来使用信号量方式在保存后马上修改密码解决。(网上一个项目的例子)

users/signals.py

from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth import get_user_model
User = get_user_model()


# post_save:上面七大方法之一:在模型保存之后的操作
# sender: 发出信号的model
@receiver(post_save, sender=User)
def create_user(sender, instance=None, created=False, **kwargs):
  """
  sender:模型类。
  instance:保存的实际实例。
  created:如果创建了新记录True。
  update_fields:Model.save()要更新的字段集,如果没有传递则为None
  """
  if created:
    password = instance.password
    # instance相当于user
    instance.set_password(password)
    instance.save()

users/apps.py

from django.apps import AppConfig


class UsersConfig(AppConfig):
  name = 'users'
  verbose_name = '用户管理'

  def ready(self):
  """使用ready加载,否则不生效"""
    import users.signals

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
Python 代码性能优化技巧分享
Aug 07 Python
Python实现的一个简单LRU cache
Sep 26 Python
使用Python脚本来获取Cisco设备信息的示例
May 04 Python
Python的string模块中的Template类字符串模板用法
Jun 27 Python
python 安装virtualenv和virtualenvwrapper的方法
Jan 13 Python
django 外键model的互相读取方法
Dec 15 Python
Python中使用pypdf2合并、分割、加密pdf文件的代码详解
May 21 Python
对python中不同模块(函数、类、变量)的调用详解
Jul 16 Python
python实现批量修改服务器密码的方法
Aug 13 Python
Python多线程模块Threading用法示例小结
Nov 09 Python
使用py-spy解决scrapy卡死的问题方法
Sep 29 Python
Python实现简单的俄罗斯方块游戏
Sep 25 Python
python利用跳板机ssh远程连接redis的方法
Feb 19 #Python
Python获取Redis所有Key以及内容的方法
Feb 19 #Python
Python元组常见操作示例
Feb 19 #Python
python redis 删除key脚本的实例
Feb 19 #Python
Python列表切片操作实例总结
Feb 19 #Python
Python常用的json标准库
Feb 19 #Python
Python List cmp()知识点总结
Feb 18 #Python
You might like
索尼SONY ICF-SW7600GR电路分析与改良
2021/03/02 无线电
用PHP制作静态网站的模板框架(二)
2006/10/09 PHP
PHP无限分类(树形类)的深入分析
2013/06/02 PHP
php购物车实现方法
2015/01/03 PHP
flash javascript之间的通讯方法小结
2008/12/20 Javascript
Jquery 组合form元素为json格式,asp.net反序列化
2009/07/09 Javascript
也说JavaScript中String类的replace函数
2011/09/22 Javascript
快速掌握jQuery插件WebUploader文件上传
2016/11/07 Javascript
JS基于设计模式中的单例模式(Singleton)实现封装对数据增删改查功能
2018/02/06 Javascript
vue将毫秒数转化为正常日期格式的实例
2018/09/16 Javascript
Vue前端项目部署IIS的实现
2020/01/06 Javascript
vue使用微信扫一扫功能的实现代码
2020/04/11 Javascript
[03:12]2016完美“圣”典风云人物:单车专访
2016/12/02 DOTA
[14:03]2017DOTA2亚洲邀请赛开幕式:12神兵演绎水墨中华
2017/04/01 DOTA
[01:04:09]DOTA2-DPC中国联赛 正赛 iG vs VG BO3 第二场 2月2日
2021/03/11 DOTA
Python实现改变与矩形橡胶的线条的颜色代码示例
2018/01/05 Python
Python之列表实现栈的工作功能
2019/01/28 Python
django中间键重定向实例方法
2019/11/10 Python
Python GUI编程学习笔记之tkinter中messagebox、filedialog控件用法详解
2020/03/30 Python
css3 clip实现圆环进度条的示例代码
2018/02/07 HTML / CSS
HTML5之SVG 2D入门9—蒙板及mask元素介绍与应用
2013/01/30 HTML / CSS
详解canvas绘制多张图的排列顺序问题
2019/01/21 HTML / CSS
英国No.1文具和办公用品在线:Euroffice
2016/09/21 全球购物
Laura Mercier官网:彩妆大师罗拉玛斯亚的化妆品牌
2018/01/04 全球购物
施华洛世奇日本官网:SWAROVSKI日本
2018/05/04 全球购物
美国受信赖的教育产品供应商:Nest Learning
2018/06/14 全球购物
保险专业大学生职业规划书
2014/03/03 职场文书
婚礼主持结束词
2014/03/13 职场文书
知识改变命运演讲稿
2014/05/21 职场文书
计算机应用专业自荐信
2014/07/05 职场文书
关于读书的演讲稿400字
2014/08/27 职场文书
2014年勤工助学工作总结
2014/11/24 职场文书
售后前台接待岗位职责
2015/04/03 职场文书
早会开场白台词大全
2015/06/01 职场文书
公司考勤管理制度
2015/08/04 职场文书
在Oracle表中进行关键词搜索的过程
2022/06/10 Oracle