1. 程式人生 > >Django Signals 從實踐到原始碼分析

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_finished
request_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,比如:

  1. django.db.models.signals.pre_save & django.db.models.signals.post_save

    Sent before or after a model’s save() method is called.

  2. 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.

  3. 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)獲取最新文章 python之禪