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