一、概述
Django includes a “signal dispatcher” which helps allow decoupled applications get notified when actions occur elsewhere in the framework. In a nutshell, signals allow certain senders to notify a set of receivers that some action has taken place. They’re especially useful when many pieces of code may be interested in the same events.
Django内部包含了一位“信号调度员”:当某事件在框架内发生时,它可以通知到我们的应用程序。 简而言之,当event(事件)发生时,signals(信号)允许若干 senders(寄件人)通知一组receivers(接收者)。这在我们多个独立的应用代码对同一事件的发生都感兴趣时,特别有用。
二、signal、receiver、sender
2.1 signal
所有signal都是django.dispatch.Signal类的实例。 Django提供了一组内置的信号集,只要用户代码事先绑定到这些信号上,当框架内某些事件发生时,用户代码可以从中获取到通知,从而执行用户代码。 以下是一些Django内预定义的signals:
django.db.models.signals.pre_save & django.db.models.signals.post_save
Sent before or after a model’s save() method is called.
django.db.models.signals.pre_delete & django.db.models.signals.post_delete
Sent before or after a model’s delete() method or queryset’s delete() method is called.
django.db.models.signals.m2m_changed
Sent when a ManyToManyField on a model is changed.
django.core.signals.request_started & django.core.signals.request_finished
Sent when Django starts or finishes an HTTP request.
当然,用户也可以自定义signal。
For example:
import django.dispatch
pizza_done = django.dispatch.Signal(providing_args=["toppings", "size"])
This declares a pizza_done signal that will provide receivers with toppings and size arguments.
定义一个名叫pizza_done的Signal实例,它提供 toppings和size字典参数给 receivers。 (疑问:查看Signal类的源码,个人觉得这个providing_args就是个‘花瓶’,貌似整个类就没有对providing_args属性有过利用,就像个口头约定,receivers能得到什么参数,还得看send()当时是怎么传的~,有明白的少侠请指点下)
2.2 receiver以及如何绑定到signal
receiver 相当于一个回调函数,相关事件发生了就会触发它被执行。
def my_callback(sender, **kwargs):
print "Request finished!"
Notice that the function takes a sender argument, along with wildcard keyword arguments (**kwargs); all signal handlers must take these arguments.
We'll look at senders a bit later, but right now look at the **kwargs argument. All signals send keyword arguments, and may change those keyword arguments at any time. In the case of request_finished, it's documented as sending no arguments, which means we might be tempted to write our signal handling as my_callback (sender).
This would be wrong -- in fact, Django will throw an error if you do so. That's because at any point arguments could get added to the signal and your receiver must be able to handle those new arguments.
上面的话我是这么理解的:为了让我们的回调函数能够支持千变万化的参数,用sender+kwargs字典作参得了。
可是每次取值都得从kwargs中取,看起来可读性也变差了。个人用法是函数中经常会用到的参数直接列出来,比如def my_callback(sender,app,**kwargs):pass。
绑定receivers到signal
有两种方式可以实现这点。手动方式:
from django.core.signals import request_finished
request_finished.connect(my_callback)
或者,你可以使用receiver装饰器来定义你的receiver:
from django.core.signals import request_finished
from django.dispatch import receiver
@receiver(request_finished)
def my_callback(sender, **kwargs):
print "Request finished!"
现在,每次request_finished信号有通知,我们就执行一次my_callback。
当你这么设置好了后,就会收到各种signals发来的通知。可是,如果我的回调哈数只对特定sender感兴趣的话怎么办呢?可以通过指定 connect 函数的 sender 参数来过滤,用装饰器方式过滤的话,像这样:
from django.db.models.signals import pre_save
from django.dispatch import receiver
from myapp.models import MyModel
@receiver(pre_save, sender=MyModel)
def my_handler(sender, **kwargs):
...
上面的回调函数my_handler只有在MyModel实例被保存时,才会被调用。
此外,connect函数还有个参数 dispatch_uid(通常是一个字符串)用来唯一标识你的回调函数,目的是为了避免重复 绑定或注册 回调函数到同一signal上,如果那样的话,一个事件发生了会进好几次回调哦。
2.3 sender
其实sender只是个参数,不只是上面说到的connect函数中的可选参数,发送信号时则必须要指定sender参数,它告诉receiver函数这个signal到底来自哪里。举个例子:
class PizzaStore(object):
...
def send_pizza(self, toppings, size):
pizza_done.send(sender=self, toppings=toppings, size=size)
...
这里,pizza_done信号被发送,sender参数是个PizzaStore实例。
除了用Signal.send(sender, kwargs)来发送信号,你还可以使用Signal.send_robust(sender,kwargs)。区别是后者会捕捉receiver回调函数中的异常,并将错误信息添加进函数返回值中。
2.4 其他
方法 Signal.disconnect([receiver=None, sender=None, weak=True, dispatch_uid=None])用于解除signal与某receiver的绑定关系。
三、举个Django中现成的例子
在你的settings.INSTALLED_APPS中添加进应用'django.contrib.contenttypes',然后运行 "manage.py syncdb",这时候你会看到数据库多一张表 “django_content_type”,而且里面会有一些纪 录呢,当然,这并没有什么好奇怪的,表django_content_type中的纪录代表着整个项目已安装上的应 用。接着,你再往settings.INSTALLED_APPS添加一些其他应用,比如我们自定义的,news应用,那么这个news的纪录要如何添加进表django_content_type中呢?答案还是"manage.py syncdb"!而这实现之中就利用到了Django的signal机制。
首先是signal定义:
#file:django\db\models\signals.py
from django.dispatch import Signal
post_syncdb = Signal(providing_args=["class", "app", "created_models",
"verbosity", "interactive"])
post_syncdb就是下面多次使用到的信号了。
其次是receiver绑定到signal:
#file:django\contrib\contenttypes\management.py(有木有觉得所有signal的connect方法都是放
在应用的management.py文件或者management目录下__init__.py文件中执行的)
from django.db.models import signals
signals.post_syncdb.connect(update_contenttypes)
update_contenttypes函数 根据新增的应用在django_content_type表中生成相应的记录。
最后是信号的触发:
#file:django\core\management\sql.py
def emit_post_sync_signal(created_models, verbosity, interactive, db):
# Emit the post_sync signal for every application.
for app in models.get_apps():
app_name = app.__name__.split('.')[-2]
if verbosity >= 2:
print("Running post-sync handlers for application %s" % app_name)
models.signals.post_syncdb.send(sender=app, app=app,
created_models=created_models, verbosity=verbosity,
interactive=interactive, db=db)
这个函数emit_post_sync_signal正是被manage.py的两大命令syncdb和flush所调用的,从而每次manage.py syncdb总是这么见效。
以上总结,是敝人所思所想,若有不对的地方,请果断拍砖~
嗨,就到这里,就到这里吧!