flask 信号机制实现

Signal in flask

Posted by Bryan on April 21, 2019

背景

flask 从 0.6 开始支持信号机制,具体的功能实现是依赖 blinker , 通过使用 blinker,在 flask 内实现了一个简单的观察者模式。在 flask 中,可以对 flask 内的一些 内置的信号 可以监察,注册相关的处理方法,在信号发出时触发。使用起来十分方便,这篇文章就来具体探索一下 blinker 的信号实现机制

信号使用

在 blinker 中,信号机制的使用十分简单,通过 Signal.connect() 注册信号处理方法, 即增加观察者,之后可以通过 Signal.send() 发出信号,此时所有的信号处理方法会被依次触发。具体的使用如下所示:

# 生成信号

ready = signal('ready')

# 注册信号

def subscriber(sender):
    print("Got a signal sent by %r" % sender)

ready.connect(subscriber)

# 发出信号

ready.send('sender A')

上面的情况最后会执行subscriber() 方法,在本例子中看起来有些多余,完全可以直接调用subscriber() 方法。但是在实际中,信号的注册方法可以是很多个,并且可以动态变化,使用了 blinker 提供的观察者模式,信号触发者不用关心一系列观察者方法的调用,从而解耦信号触发者和观察者。

具体实现

blinker 中代码的实现也十分简单,对其主要代码简化后如下所示:

class Signal(object):
    def __init__(self, doc=None):
        # 建立 receiver 的 id 与 receiver 的映射
        
        self.receivers = {}
        # 保存 sender_id 与 receiver 对应 id 列表的映射
        
        self._by_sender = defaultdict(set)   

    # 注册信号触发的方法,可以用 sender 进行过滤
    # 如果指定了 sender, 则只会在此信号触发并传递与 sender 等同的参数时响应
    # 如果不指定 sender, 那么会在此信号触发传递任意参数都会响应
    
    def connect(self, receiver, sender=ANY, weak=True):
        receiver_id = hashable_identity(receiver)

        if sender is ANY:
            sender_id = ANY_ID
        else:
            sender_id = hashable_identity(sender)
        #存储 receiver_id 与 receiver 映射
        
        self.receivers.setdefault(receiver_id, receiver) 
        # 存储 sender_id 与 receiver_id 列表映射
        
        self._by_sender[sender_id].add(receiver_id) 
        return receiver

    # 触发信号注册的方法
    
    def send(self, *sender, **kwargs):
        if not self.receivers:
            return []

         # 依次调用参数 sender 对应注册的方法
        
        return [(receiver, receiver(sender, **kwargs))
                for receiver in self.receivers_for(sender)]

    # 获取 sender 对应的注册的方法
    
    def receivers_for(self, sender):
        if self.receivers:
            sender_id = hashable_identity(sender)
            if sender_id in self._by_sender:
                 # 获取 sender_id 或任意参数对应注册的方法
                
                ids = (self._by_sender[ANY_ID] |
                       self._by_sender[sender_id])
            else:
                ids = self._by_sender[ANY_ID].copy()

            for receiver_id in ids:
                # 获取 receiver_id 对应的方法
                
                receiver = self.receivers.get(receiver_id) 
                yield receiver

信号注册

通过上面的代码可以看到,通过 Signal 类创建信号后,可以调用Signal.connect() 方法注册处理方法,在注册时,可以使用 sender 参数指定只对特定的参数才会触发执行,还是对此信号的任意参数的触发都执行,默认是对任意参数都会触发执行。注册之后就会存储相应的注册信息至Signal 类对应的receivers_by_sender 属性中

信号触发

在执行Signal.send() 方法触发信号时,会从 _by_sender 属性中查找到此次触发参数对应的注册的方法的 id 列表,然后利用 id 列表从 receivers 中查找到注册的处理方法,依次执行注册的处理方法。

对 blinker 的简单理解为可以通过将注册的方法列表保存至对象的属性中,之后可以简单的触发这个方法列表。

总结

上面的介绍是一个 blinker 中的一个简单实现,blinker 内部还有一些更复杂一些,包括存储原始触发方法的弱引用优化,有兴趣的同志们可以深入研究一下。但是最核心的内容无非就是这些了。可以看到,一个被广泛使用的库也没有做什么神奇的事情。越来越能理解 linux 的哲学,每个工具都应该追求做少做好。