深入理解Django-Signals信号量

yipeiwu_com5年前Python基础

定义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设计】。

相关文章

关于numpy中np.nonzero()函数用法的详解

np.nonzero函数是numpy中用于得到数组array中非零元素的位置(数组索引)的函数。一般来说,通过help(np.nonzero)能够查看到该函数的解析与例程。但是,由于例程...

python SMTP实现发送带附件电子邮件

本文实例为大家分享了python SMTP发送带附件电子邮件的具体代码,供大家参考,具体内容如下 可采用email模块发送电子邮件附件。发送一个未知MIME类型的文件附件其基本思路如下:...

Python导入oracle数据的方法

本文实例讲述了Python导入oracle数据的方法。分享给大家供大家参考。具体如下: import cx_Oracle dns_tns=cx_Oracle.makedsn("1...

Python中协程用法代码详解

本文研究的主要是python中协程的相关问题,具体介绍如下。 Num01–>协程的定义 协程,又称微线程,纤程。英文名Coroutine。 首先我们得知道协程是啥?协程其实可以...

python适合人工智能的理由和优势

Python借助AI和数据科学,目前已经攀爬到了编程语言生态链的顶级位置,可以说Python基本上与AI已经紧密捆绑在了一起了。为什么人工智能开发要使用到python语言?小编认为基于以...