Django Signals 從實踐到原始碼分析
當某個事件發生的時候,signal(訊號)允許senders(傳送者)用來通知receivers(接收者),通知receivers幹嘛?你想要recivers幹嘛就可以幹嘛。這在多處程式碼對同一個事件感興趣的時候就有用武之地了。 比如:Django提供了一個built-in signal,叫django.core.signals.request_finished
,這個signal會在一個HTTP請求完成後傳送。下面就用一個簡單的例項說明:在每個請求完成後列印"request finished"
編寫receiver
reciver是一個普通的callable物件,簡單來說就是一個可被呼叫的函式,但是需要注意的是它需要接收一個引數sender
**kwargs
def my_callback(sender, **kwargs): ''' 這是個receiver函式 你可以在這裡做愛做的的事情 ''' print sender print kwargs print("Request finished!")
這裡我們先撇開sender和kwargs後面再分析,reciver函式寫好之後,就需要把request_finished
訊號連線(註冊)到my_callback
。
from django.core.signals import request_finishedrequest_finished.connect(my_callback)
現在請求一個URL路徑/hello
,後臺列印的結果:
[31/Mar/2014 21:52:33] "GET /hello/ HTTP/1.1" 200 263 <class 'django.core.handlers.wsgi.WSGIHandler'> {'signal': <django.dispatch.dispatcher.Signal object at 0x0262E510>} Request finished!
以上就是一個signal的執行流程,那麼django內部是怎麼實現的呢?為什麼呼叫了reciver.connect後,my_callback就能得到執行了呢?且看原始碼分析:
request_finished定義在檔案django.core.signals.py裡面:
from django.dispatch import Signal request_started = Signal() request_finished = Signal() got_request_exception = Signal(providing_args=["request"])
request_finished
就是Signal的例項。GET請求完成後會執行my_callback
方法,為什麼這麼神奇,我們順著request_finished的思路來猜想,既然是請求完成了,那麼此時response物件也生成了,那麼神奇的事情一定是在response裡面發生的。去response.py檔案裡面看看:django.http.response.py
def close(self): for closable in self._closable_objects: try: closable.close() except Exception: pass signals.request_finished.send(sender=self._handler_class)
看到在response的close方法裡面有send方法,而且這個sender就是我們在前面看到的django.core.handlers.wsgi.WSGIHandler'
,這個send方法會發送訊號給所有的receivers。
#Signal.send方法的原始碼: 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
注意:你可以看到在for迴圈裡面迭代的呼叫的receiver方法。以上就是django內部的執行原理。思考下send方式是signal的而不是sender的呢?從面向物件的角度來說,誰是物件的擁有者,誰就提供相應的方法。比如汽車的drive方法肯定是由汽車提供而不是由人。
小結
我們需要做的只是編寫receiver,然後呼叫signal.connect方法,相當於把receiver註冊到signal上去。當事件觸發時,相應的signal就會通知所有註冊的receivers得到呼叫。尼瑪,這是傳說中的觀察者模式。
連線receiver函式還有另外一個方法,用裝飾器:
@receiver(request_finished): def my_handler(sender, **kwages): '''
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.
signal還可以指定具體的senders,比如pre_save這個signal是在Model物件儲存在被髮送,但是我希望只有某一類Model儲存的時候才傳送,你就可以指定:
@receiver(pre_save, MyModel): def my_handle(sender, **kwargs): pass
這樣每次只有儲存MyModel例項後才會傳送,其他的XXModel就會忽略掉。
完!
關注公眾號「Python之禪」(id:vttalk)獲取最新文章