Django 源码解读-signal实现(二)

上文中分析了signal的使用场景以及使用的设计模式Django 源码解读-signal实现(一)

本文将继续分析signal在django中的具体实现,首先我们说下开发中可能遇到的问题:

  1. 我们定义在signals.py文件中的所有注册事件都未执行
  2. 在signal中的部分注册事件未执行

那么可能的原因:

  1. 所有事件未执行--检查是否在apps.py中import signals
  2. 部分注册时间未执行-检查是否注册函数名重复

我们接下来分析一下为什么会上述原因可能导致相应的错误呢?分析一下django signal源码:

1.先看看receiver的源码


def receiver(signal, **kwargs):

    """

    A decorator for connecting receivers to signals. Used by passing in the

    signal (or list of signals) and keyword arguments to connect::

        @receiver(post_save, sender=MyModel)

        def signal_receiver(sender, **kwargs):

            ...

        @receiver([post_save, post_delete], sender=MyModel)

        def signals_receiver(sender, **kwargs):

            ...

    """

    def _decorator(func):

        if isinstance(signal, (list, tuple)):

            for s in signal:

                s.connect(func, **kwargs)

        else:

            signal.connect(func, **kwargs)

        return func

    return _decorator

receiver是一个decorator,它执行了signal的connect(func, **kwargs);

# signals.py 
@receiver(post_save, sender=Order)
def order_change(sender, instance=None, created=False, **kwargs):
    if not created and instance:
        if instance.pay_status == "CONFIRMED":
            # 发送订单信息到消息队列

注意: 当signals被import的时候,receiver 装饰器就会执行

2.再看看connect执行什么操作;


WEAKREF_TYPES = (weakref.ReferenceType, saferef.BoundMethodWeakref)


def _make_id(target):
    if hasattr(target, '__func__'):
        return (id(target.__self__), id(target.__func__))
    return id(target)
NONE_ID = _make_id(None)

# A marker for caching
NO_RECEIVERS = object()


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

    Internal attributes:

        receivers
            { receriverkey (id) : weakref(receiver) }
    """
    def __init__(self, providing_args=None, use_caching=False):
        """
        Create a new signal.

        providing_args
            A list of the arguments this signal can pass along in a send() call.
        """
        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 {}

    def connect(self, receiver, sender=None, weak=True, dispatch_uid=None):
        """
        Connect receiver to sender for signal.

        Arguments:

            receiver
                A function or an instance method which is to receive signals.
                Receivers must be hashable objects.

                If weak is True, then receiver must be weak-referencable (more
                precisely saferef.safeRef() must be able to create a reference
                to the receiver).

                Receivers must be able to accept keyword arguments.

                If receivers have a dispatch_uid attribute, the receiver will
                not be added if another receiver already exists with that
                dispatch_uid.

            sender
                The sender to which the receiver should respond. Must either be
                of type Signal, or None to receive events from any sender.

            weak
                Whether to use weak references to the receiver. By default, the
                module will attempt to use weak references to the receiver
                objects. If this parameter is false, then strong references will
                be used.

            dispatch_uid
                An identifier used to uniquely identify a particular instance of
                a receiver. This will usually be a string, though it may be
                anything hashable.
        """
        from django.conf import settings

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

            # Check for **kwargs
            # Not all callables are inspectable with getargspec, so we'll
            # try a couple different ways but in the end fall back on assuming
            # it is -- we don't want to prevent registration of valid but weird
            # callables.
            try:
                argspec = inspect.getargspec(receiver)
            except TypeError:
                try:
                    argspec = inspect.getargspec(receiver.__call__)
                except (TypeError, AttributeError):
                    argspec = None
            if argspec:
                assert argspec[2] is not None, \
                    "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:
            receiver = saferef.safeRef(receiver, onDelete=self._remove_receiver)

        with self.lock:
            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 send(self, sender, **named):
        """
        Send signal from sender to all connected receivers.

        If any receiver raises an error, the error propagates back through send,
        terminating the dispatch loop, so it is quite possible to not have all
        receivers called if a raises an error.

        Arguments:

            sender
                The sender of the signal Either a specific object or None.

            named
                Named arguments which will be passed to receivers.

        Returns a list of tuple pairs [(receiver, response), ... ].
        """
        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

可以看出signal中的connect执行的最关键的操作是,

self.receivers.append((lookup_key, receiver))
  • connect函数:将receiver(由receiver decorator装饰的自定义函数 )添加到self.receivers;
    lookup_key 是根据receiver和sender(order model)生成的一个值,目的是防止重复注册;

如果我们自定义函数名重复,sender也相同的情况下,就会导致文件中后面定义的函数覆盖前面定义函数,也就引入了我们前面谈到的第二个bug;

  • send 函数: 遍历活着的receiver,依次执行receiver函数()
response = receiver(signal=self, sender=sender, **named)

3. 查看Model的实现 models/base.py

class Model(six.with_metaclass(ModelBase)):
  def save(self, force_insert=False, force_update=False, using=None,
             update_fields=None):
       # 省略部分代码
        self.save_base(using=using, force_insert=force_insert,
                       force_update=force_update, update_fields=update_fields)
    save.alters_data = True
def save_base(self, raw=False, force_insert=False,
                  force_update=False, using=None, update_fields=None):
        """
        Handles the parts of saving which should be done only once per save,
        yet need to be done in raw saves, too. This includes some sanity
        checks and signal sending.

        The 'raw' argument is telling save_base not to save any parent
        models and not to do any changes to the values before save. This
        is used by fixture loading.
        """
        using = using or router.db_for_write(self.__class__, instance=self)
        assert not (force_insert and (force_update or update_fields))
        assert update_fields is None or len(update_fields) > 0
        cls = origin = self.__class__
        # Skip proxies, but keep the origin as the proxy model.
        if cls._meta.proxy:
            cls = cls._meta.concrete_model
        meta = cls._meta
        if not meta.auto_created:
            signals.pre_save.send(sender=origin, instance=self, raw=raw, using=using,
                                  update_fields=update_fields)
        with transaction.commit_on_success_unless_managed(using=using, savepoint=False):
            if not raw:
                self._save_parents(cls, using, update_fields)
            updated = self._save_table(raw, cls, force_insert, force_update, using, update_fields)
        # Store the database on which the object was saved
        self._state.db = using
        # Once saved, this is no longer a to-be-added instance.
        self._state.adding = False

        # Signal that the save is complete
        if not meta.auto_created:
            signals.post_save.send(sender=origin, instance=self, created=(not updated),
                                   update_fields=update_fields, raw=raw, using=using)

    save_base.alters_data = True
  • save() 函数调用了save_base()
    在save_base中,调用了
  signals.post_save.send(sender=origin, instance=self, created=(not updated),
                                   update_fields=update_fields, raw=raw, using=using)

通过上面的分析我们已经知道,
1.当import signals执行时,就会向signals的post_save 实例注册事件,回调函数就是receiver;
2.当model的save被执行的时候,就会调用post_save的send函数,向注册的receiver发送通知;

signals的执行流程如下:

signals.png
  1. apps.py 文件内import signals,执行向post_save 注册回调函数receiver
  2. models执行save()时候,调用post_save的send()
  3. send函数遍历所有receiver并执行回调函数(receiver 装饰的自定义函数),因此我们的逻辑就会被运行;

好了分析完了整个过程,我们就知道了,如果我们的signals文件下的所有receiver回调都没有被执行,可能是忘记了在apps.py中import signals,导致没有向signal 实例注册回调。

你可能感兴趣的:(Django 源码解读-signal实现(二))