blinker是一個python的信號庫,既支持簡單的對象到對象通信,也支持針對多個對象進行組播,信號就是在框架的核心功能或者一些Flask拓展發生動作時所發送的通知,可以幫助解耦應用,解耦就是模塊間的耦合嚴重了,修改一些東西,可能會牽扯到很多其他的地方,所以需要減少類之間的相互依賴,所以需要代碼解耦。
先來一個例子:
創建信號併發送消息
- 參數:信號名,信號的發送者
- 作用:根據信號名創建信號,然後發送信號,參數是發送者
def signal_send(name, source='anoymous', **kw):
name_signal = signal(name) # 這裏根據name創建一個信號
ret = name_signal.send(source, **kw) # 發送一個信號
return ret
接受信號
- 參數:信號名,訂閱者(用於收到信號就調用函數)
- 作用:得到信號對象,綁定訂閱者
def signal_listen(name, handler):
name_signal = signal(name) # 這裏其實沒有創建新的信號,就是之前的name信號
s.connect(handler, weak=False) # 綁定了訂閱者
信號接受裝飾器:
- 參數:信號名
- 針對的func:裝飾器原理是針對func的,傳入的func就是這個信號的訂閱者
def signal_handle(name, *args, **kw):
s_name = name
def wrapper(func):
def inner(*args, **kw):
func(*args, **kw)
signal_listen(s_name, func)
return inner
return wrapper
使用上面的套件:
# 這裏監聽了信號test,一旦收到消息就調用裏面的函數
@signal_handle('test')
def recv(sender, **kw):
print("Got a signal sent by %r, data %r" % (sender, kw))
return 'receive ok'
signal_send('test', source='123', test=1234)
# 這裏相當於創建了信號,並且發送了消息123
# 輸出是Got a signal sent by '123', data {'test': 1234}
創建信號:
>>> from blinker import signal
>>> initialized = signal("initialized")
>>> initialized is signal("initialized")
True
可以看到信號是通過singal()方法創建的,而且當傳入的參數一致時,調用該方法都是返回同一個信號對象。
訂閱信號,即信號觸發就會調用函數
>>> def subscriber(sender): # 這個函數就是**訂閱者!!!!!**
... print("Got a signal sent by %r" % sender)
...
>>> ready = signal('ready') # 創建信號
>>> ready.connect(subscriber) # 註冊一個函數,只要觸發信號就調用該函數
信號觸發的函數會以觸發信號的對象作爲參數!
信號的觸發:
使用Signal.send()方法來通知信號訂閱者,即調用信號註冊的函數
>>> class Processor:
... def __init__(self, name):
... self.name = name
...
... def go(self):
... ready = signal('ready') # 這裏的信號對象還是上面創建的信號對象
ready.connect(xxx) # 這裏綁定一個函數
... ready.send(self)
# send()的參數是self,所以這個類的實例就是信號的發送者
... print("Processing.")
... complete = signal('complete')
# 沒有註冊信號訂閱者的信號也可以觸發該信號,但是什麼信號都不會發送
... complete.send(self)
...
... def __repr__(self):
... return '<Processor %s>' % self.name
...
>>> processor_a = Processor('a') # 創建了一個對象
>>> processor_a.go() # 這裏相當於發送了一個信號,信號的發送者是自己本身這個對象
Got a signal sent by <Processor a>
Processing.
訂閱特定的發佈者,即只有特定的發送者才能觸發這個訂閱函數
默認情況下,所有的發送者都能觸發信號,通知訂閱者,但是可以在connect()中傳遞一個可選參數sender=?
>>> def b_subscriber(sender):
... print("Caught signal from processor_b.")
... assert sender.name == 'b'
...
>>> processor_b = Processor('b')
>>> ready.connect(b_subscriber, sender=processor_b) # 這裏指定特定的發送者
<function b_subscriber at 0x...>
這樣的話,Processor實例化出來的其他對象都不會通知這個信號訂閱者
通過信號收發數據:
>>> send_data = signal('send-data') # 這裏創建信號對象
>>> @send_data.connect # 綁定信號訂閱者,相當於signal.connect(subscriber)
... def receive_data(sender, **kw):
... print("Caught signal from %r, data %r" % (sender, kw))
... return 'received!'
...
>>> result = send_data.send('anonymous', abc=123) # send()方法可以加上額外的關鍵字參數
Caught signal from 'anonymous', data {'abc': 123}
匿名信號
前面創建的信號都是命名信號,每次調用都會創建一個唯一的信號,還可以將signal作爲類屬性
>>> from blinker import Signal
>>> class AltProcessor:
... on_ready = Signal() # 這裏創建了兩個匿名信號
... on_complete = Signal()
...
... def __init__(self, name):
... self.name = name
...
... def go(self):
... self.on_ready.send(self)
... print("Alternate processing.")
... self.on_complete.send(self)
...
... def __repr__(self):
... return '<AltProcessor %s>' % self.name
使用裝飾器綁定訂閱函數
可以使用:
@signal.connect
的方式來指定訂閱者,但是這樣就不能指定特定的發送者。
就可以使用:
@signal.connect_via(x)
x就是指定了sender
優化信號的發送,檢查該信號是否有訂閱者
>>> bool(signal('ready').receivers)
True
>>> signal('ready').has_receivers_for(processor_a)
True