上文中分析了signal的使用场景以及使用的设计模式Django 源码解读-signal实现(一)
本文将继续分析signal在django中的具体实现,首先我们说下开发中可能遇到的问题:
- 我们定义在signals.py文件中的所有注册事件都未执行
- 在signal中的部分注册事件未执行
那么可能的原因:
- 所有事件未执行--检查是否在apps.py中import signals
- 部分注册时间未执行-检查是否注册函数名重复
我们接下来分析一下为什么会上述原因可能导致相应的错误呢?分析一下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的执行流程如下:
- apps.py 文件内import signals,执行向post_save 注册回调函数receiver
- models执行save()时候,调用post_save的send()
- send函数遍历所有receiver并执行回调函数(receiver 装饰的自定义函数),因此我们的逻辑就会被运行;
好了分析完了整个过程,我们就知道了,如果我们的signals文件下的所有receiver回调都没有被执行,可能是忘记了在apps.py中import signals,导致没有向signal 实例注册回调。