浅谈Django的Signals的实现
在说Signals的实现之前,需要先了解一下发布/订阅模型
模型介绍
发布订阅是一种消息范式,消息的发送者不会将消息直接发送给特定的接收者。而是将消息发送某个频道上,无需了解哪些订阅者可能存在。同样的,订阅者可以表达对一个或多个频道的兴趣,只接收感兴趣的消息,无需了解哪些发布者存在。
发布者
即消息的生产者。
频道
一个消息的通道,标识发布者需要将消息发送到哪一个消息通道。
订阅者
即消息接收者。
Django提供的Signals
与model有关的signals
from django.db.models.signals import class_prepared
from django.db.models.signals import pre_init, post_init
from django.db.models.signals import pre_save, post_save
from django.db.models.signals import pre_delete, post_delete
from django.db.models.signals import m2m_changed
from django.db.models.signals import pre_migrate, post_migrate
from django.db.backends.signals import connection_created
与请求有关的signals
from django.core.signals import request_finished
from django.core.signals import request_started
from django.core.signals import got_request_exception
from django.core.signals import setting_changed
与测试有关的signals
from django.test.signals import setting_changed
from django.test.signals import template_rendered
关于signals的使用就不说明了。
Signals实现
创建一个发布者类
from typing import List, Tuple
class Signal:
def __init__(self):
self.receivers: List[Tuple[Tuple[int, int], callable]] = []
def send(self, sender, **kwargs):
return [
(receiver, receiver(signal=self, sender=sender, **kwargs))
for receiver in self.live_receivers(sender=sender)
]
def connect(self, receiver: callable, sender=None):
lookup_key: Tuple[int, int] = (id(receiver), id(sender))
self.receivers.append((lookup_key, receiver))
def disconnect(self, receiver: callable, sender=None):
lookup_key: Tuple[int, int] = (id(receiver), id(sender))
self.receivers.remove((lookup_key, receiver))
def live_receivers(self, sender) -> List[callable]:
receivers: List[callable] = []
sender_key = id(sender)
for (receiver_id, sender_id), receiver in self.receivers:
if sender_key == sender_id:
receivers.append(receiver)
return receivers
def subscribe(signal, **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
实例化发布者
pre_init = Signal()
实例化一个发布者,pre_init
可以在初始化一个实例之前通知订阅者。
创建支持通知的一个类
class Test:
def __init__(self, *args, **kwargs):
cls = self.__class__
pre_init.send(sender=cls, args=args, kwargs=kwargs)
for key, value in kwargs.items():
setattr(self, key, value)
订阅pre_intit
消息
@subscribe(pre_init, sender=Model)
def test_pre_init(sender, **kwargs):
print("-------pre_init_______")
print(sender)
print(kwargs)
print("-------end_pre_init_______")
使用
Test(a=1, b=2)
这个时候便会输出test_pre_init
中相应的信息
思考
First
Q
如果我有多个类都通知了pre_init
,而test_pre_init
只想处理某个特定的类发出的消息,应该如何实现?
A
在这个例子中,发送者就是Test
类,接收者就是test_pre_init
方法,消息通道则是pre_init
。当执行send
方法时,由pre_init
来筛选需要发送给哪些接收者。接收者在注册的时候就已经指定了我要接收谁的消息。
Second
Q
由pre_init
筛选,时间复杂度太高,为O(n),如何降低时间复杂度。
A
可以换一种存储方式,改成dict来存储接收者。可以把时间复杂度降为O(1),也可以由接收者来控制,当消息来的时候由接收者来选择是否接收该信息。