Django包含一個(gè)“信號(hào)的分發(fā)器”,允許解耦的應(yīng)用在信號(hào)出現(xiàn)在框架的任何地方時(shí),都能獲得通知。簡(jiǎn)單來說,信號(hào)允許指定的 _發(fā)送器_通知一系列的接收器,一些操作已經(jīng)發(fā)生了。當(dāng)一些代碼會(huì)相同事件感興趣時(shí),會(huì)十分有幫助。
Django 提供了一系列的內(nèi)建信號(hào),允許用戶的代碼獲得DJango的特定操作的通知。這包含一些有用的通知:
django.db.models.signals.pre_save & django.db.models.signals.post_save
在模型 save()方法調(diào)用之前或之后發(fā)送。
django.db.models.signals.pre_delete & django.db.models.signals.post_delete
django.db.models.signals.m2m_changed
模型上的 ManyToManyField 修改時(shí)發(fā)送。
django.core.signals.request_started & django.core.signals.request_finished
Django建立或關(guān)閉HTTP 請(qǐng)求時(shí)發(fā)送。
關(guān)于完整列表以及每個(gè)信號(hào)的完整解釋,請(qǐng)見內(nèi)建信號(hào)的文檔 。
你也可以定義和發(fā)送你自己的自定義信號(hào);見下文。
你需要注冊(cè)一個(gè)_接收器_函數(shù)來接受信號(hào),它在信號(hào)使用Signal.connect()發(fā)送時(shí)被調(diào)用:
Signal.``connect(receiver[, sender=None, weak=True, _dispatchuid=None])
| Parameters: | * **receiver** – 和這個(gè)信號(hào)連接的回調(diào)函數(shù)。詳見[_接收器函數(shù)_](#receiver-functions)。 * **sender** – 指定一個(gè)特定的發(fā)送器,來從它那里接受信號(hào)。詳見[_連接由指定發(fā)送器發(fā)送的信號(hào)_](#connecting-to-specific-signals)。 * **weak** – DJango通常以弱引用儲(chǔ)存信號(hào)處理器。這就是說,如果你的接收器是個(gè)局部變量,可能會(huì)被垃圾回收。當(dāng)你調(diào)用信號(hào)的`connect()`方法是,傳遞?`weak=False`來防止這樣做。 * **dispatch_uid** – 一個(gè)信號(hào)接收器的唯一標(biāo)識(shí)符,以防信號(hào)多次發(fā)送。詳見[_防止重復(fù)的信號(hào)_](#preventing-duplicate-signals)。 |
|---|
讓我們來看一看它如何通過注冊(cè)在每次在HTTP請(qǐng)求結(jié)束時(shí)調(diào)用的信號(hào)來工作。我們將會(huì)連接到request_finished 信號(hào)。
首先,我們需要定義接收器函數(shù)。接受器可以是Python函數(shù)或者方法:
def my_callback(sender, **kwargs):
print("Request finished!")
注意函數(shù)接受sender函數(shù),以及通配符關(guān)鍵字參數(shù)(**kwargs);所有信號(hào)處理器都必須接受這些參數(shù)。
我們過一會(huì)兒再關(guān)注發(fā)送器,現(xiàn)在先看一看**kwargs參數(shù)。所有信號(hào)都發(fā)送關(guān)鍵字參數(shù),并且可以在任何時(shí)候修改這些關(guān)鍵字參數(shù)。在 request_finished的情況下,它被記錄為不發(fā)送任何參數(shù),這意味著我們可能需要像my_callback(sender)一樣編寫我們自己的信號(hào)處理器。
這是錯(cuò)誤的 -- 實(shí)際上,如果你這么做了,Django會(huì)拋出異常。這是因?yàn)闊o論什么時(shí)候信號(hào)中添加了參數(shù),你的接收器都必須能夠處理這些新的參數(shù)。
有兩種方法可以將一個(gè)接收器連接到信號(hào)。你可以采用手動(dòng)連接的方法:
from django.core.signals import request_finished
request_finished.connect(my_callback)
或者使用receiver() 裝飾器來自動(dòng)連接:
receiver(signal)
| Parameters: | **signal** – A signal or a list of signals to connect a function to. |
|---|
下面是使用裝飾器連接的方法:
from django.core.signals import request_finished
from django.dispatch import receiver
@receiver(request_finished)
def my_callback(sender, **kwargs):
print("Request finished!")
現(xiàn)在,我們的my_callback函數(shù)會(huì)在每次請(qǐng)求結(jié)束時(shí)調(diào)用。
這段代碼應(yīng)該放在哪里?
嚴(yán)格來說,信號(hào)處理和注冊(cè)的代碼應(yīng)該放在你想要的任何地方,但是推薦避免放在應(yīng)用的根模塊和models模塊中,以盡量減少產(chǎn)生導(dǎo)入代碼的副作用。
實(shí)際上,信號(hào)處理通常定義在應(yīng)用相關(guān)的signals子模塊中。信號(hào)接收器在你應(yīng)用配置類中的ready() 方法中連接。如果你使用;額 receiver()裝飾器,只是在ready()內(nèi)部導(dǎo)入signals子模塊就可以了。
Changed in Django 1.7:
由于ready()并不在Django之前版本中存在,信號(hào)的注冊(cè)通常在models模塊中進(jìn)行。
注意
ready() 方法會(huì)在測(cè)試期間執(zhí)行多次,所以你可能想要防止重復(fù)的信號(hào),尤其是打算在測(cè)試中發(fā)送它們的情況。
一些信號(hào)會(huì)發(fā)送多次,但是你只想接收這些信號(hào)的一個(gè)確定的子集。例如,考慮 django.db.models.signals.pre_save 信號(hào),它在模型保存之前發(fā)送。大多數(shù)情況下,你并不需要知道 _任何_模型何時(shí)保存 -- 只需要知道一個(gè)_特定的_模型何時(shí)保存。
在這些情況下,你可以通過注冊(cè)來接收只由特定發(fā)送器發(fā)出的信號(hào)。對(duì)于django.db.models.signals.pre_save的情況, 發(fā)送者是被保存的模型類,所以你可以認(rèn)為你只需要由某些模型發(fā)出的信號(hào):
from django.db.models.signals import pre_save
from django.dispatch import receiver
from myapp.models import MyModel
@receiver(pre_save, sender=MyModel)
def my_handler(sender, **kwargs):
...
my_handler函數(shù)只在MyModel實(shí)例保存時(shí)被調(diào)用。
不同的信號(hào)使用不同的對(duì)象作為他們的發(fā)送器;對(duì)于每個(gè)特定信號(hào)的細(xì)節(jié),你需要查看內(nèi)建信號(hào)的文檔。
在一些情況下,向接收者發(fā)送信號(hào)的代碼可能會(huì)執(zhí)行多次。這會(huì)使你的接收器函數(shù)被注冊(cè)多次,并且導(dǎo)致它對(duì)于同一信號(hào)事件被調(diào)用多次。
如果這樣的行為會(huì)導(dǎo)致問題(例如在任何時(shí)候模型保存時(shí)使用信號(hào)來發(fā)送郵件),傳遞一個(gè)唯一的標(biāo)識(shí)符作為 dispatch_uid參數(shù)來標(biāo)識(shí)你的接收器函數(shù)。標(biāo)識(shí)符通常是一個(gè)字符串,雖然任何可計(jì)算哈希的對(duì)象都可以。最后的結(jié)果是,對(duì)于每個(gè)唯一的dispatch_uid值,你的接收器函數(shù)都只被信號(hào)調(diào)用一次:
from django.core.signals import request_finished
request_finished.connect(my_callback, dispatch_uid="my_unique_identifier")
你的應(yīng)用可以利用信號(hào)功能來提供自己的信號(hào)。
class Signal([_providingargs=list])
所有信號(hào)都是 django.dispatch.Signal 的實(shí)例。providing_args是一個(gè)列表,包含參數(shù)的名字,它們由信號(hào)提供給監(jiān)聽者。理論上是這樣,但是實(shí)際上并沒有任何檢查來保證向監(jiān)聽者提供了這些參數(shù)。
例如:
import django.dispatch
pizza_done = django.dispatch.Signal(providing_args=["toppings", "size"])
這段代碼聲明了pizza_done信號(hào),它向接受者提供toppings和 size 參數(shù)。
要記住你可以在任何時(shí)候修改參數(shù)的列表,所以首次嘗試的時(shí)候不需要完全確定API。
Django中有兩種方法用于發(fā)送信號(hào)。
Signal.``send(sender, **kwargs)
Signal.``send_robust(sender, **kwargs)
調(diào)用 Signal.send()或者Signal.send_robust()來發(fā)送信號(hào)。你必須提供sender 參數(shù)(大多數(shù)情況下它是一個(gè)類),并且可以提供盡可能多的關(guān)鍵字參數(shù)。
例如,這樣來發(fā)送我們的pizza_done信號(hào):
class PizzaStore(object):
...
def send_pizza(self, toppings, size):
pizza_done.send(sender=self.__class__, toppings=toppings, size=size)
...
send() 和send_robust()都會(huì)返回一個(gè)含有二元組的列表 [(receiver, response), ...],它代表了被調(diào)用的接收器函數(shù)和他們的響應(yīng)值。
send() 與 send_robust()在處理接收器函數(shù)產(chǎn)生的異常時(shí)有所不同。send()不會(huì) 捕獲任何由接收器產(chǎn)生的異常。它會(huì)簡(jiǎn)單地讓錯(cuò)誤往上傳遞。所以在錯(cuò)誤產(chǎn)生的情況,不是所有接收器都會(huì)獲得通知。
send_robust()捕獲所有繼承自Python Exception類的異常,并且確保所有接收器都能得到信號(hào)的通知。如果發(fā)生了錯(cuò)誤,錯(cuò)誤的實(shí)例會(huì)在產(chǎn)生錯(cuò)誤的接收器的二元組中返回。
New in Django 1.8:
調(diào)用send_robust()的時(shí)候,所返回的錯(cuò)誤的__traceback__屬性上會(huì)帶有 traceback。
Signal.``disconnect([receiver=None, sender=None, weak=True, _dispatchuid=None])
調(diào)用Signal.disconnect()來斷開信號(hào)的接收器。 Signal.connect()中描述了所有參數(shù)。如果接收器成功斷開,返回 True ,否則返回False。
receiver參數(shù)表示要斷開的已注冊(cè)接收器。如果dispatch_uid 用于定義接收器,可以為None。
Changed in Django 1.8:
增加了返回的布爾值。
譯者:Django 文檔協(xié)作翻譯小組,原文:Signals。
本文以 CC BY-NC-SA 3.0 協(xié)議發(fā)布,轉(zhuǎn)載請(qǐng)保留作者署名和文章出處。
Django 文檔協(xié)作翻譯小組人手緊缺,有興趣的朋友可以加入我們,完全公益性質(zhì)。交流群:467338606。