Django中的Signal代碼詳解
本文研究的主要是Django開發(fā)中的signal 的相關(guān)內(nèi)容,具體如下。
前言
在web開發(fā)中, 你可能會(huì)遇到下面這種場(chǎng)景:
在用戶完成某個(gè)操作后, 自動(dòng)去執(zhí)行一些后續(xù)的操作. 譬如用戶完成修改密碼后,
你要發(fā)送一份確認(rèn)郵件.
當(dāng)然可以把邏輯寫在一起,但是有個(gè)問題是,觸發(fā)操作一般不止一種(如用戶更改了其它信息的確認(rèn)郵件),這時(shí)候這個(gè)邏輯會(huì)需要寫多次,所以你可能會(huì)想著DRY(Don't repeat yourself),于是你把它寫到了一個(gè)函數(shù)中,每次調(diào)用。當(dāng)然這是沒問題的.
但是, 如果你換個(gè)思路你會(huì)發(fā)現(xiàn)另一個(gè)完全不同的方案, 即:
- 類似于daemon的程序監(jiān)聽著特定的事件
- 前置操作來觸發(fā)相應(yīng)的事件
- 監(jiān)聽程序執(zhí)行對(duì)應(yīng)的操作
這樣的好處是什么呢?
- 松耦合(不用把后續(xù)操作寫在主邏輯中)
- 便于復(fù)用(這也是為什么django本身,及第三方應(yīng)用如pinax大量使用此技術(shù)的原因),在各種高級(jí)語言中都會(huì)有類似的特性,如java,javascript等,而在django中我們使用signal。
觀察者模式
Siganl是Django框架中提供的一個(gè) “信號(hào)分發(fā)器”。它是設(shè)計(jì)模式中經(jīng)常提到的觀察者模式的一個(gè)實(shí)現(xiàn)應(yīng)用。
在此種模式中,一個(gè)目標(biāo)物件管理所有相依于它的觀察者物件,并且在它本身的狀態(tài)改變時(shí)主動(dòng)發(fā)出通知。這通常透過呼叫各觀察者所提供的方法來實(shí)現(xiàn)。
觀察者模式的使用場(chǎng)景
- 關(guān)聯(lián)行為場(chǎng)景,需要注意的是,關(guān)聯(lián)行為是可拆分的,而不是“組合”關(guān)系。
- 事件多級(jí)觸發(fā)場(chǎng)景。
- 跨系統(tǒng)的消息交換場(chǎng)景,如消息隊(duì)列、事件總線的處理機(jī)制。
優(yōu)點(diǎn)
1.解除耦合,讓耦合的雙方都依賴于抽象,從而使得各自的變換都不會(huì)影響另一邊的變換。
它在被觀察者和觀察者之間建立一個(gè)抽象的耦合。被觀察者角色所知道的只是一個(gè)具體觀察者列表,每一個(gè)具體觀察者都符合一個(gè)抽象觀察者的接口。被觀察者并不認(rèn)識(shí)任何一個(gè)具體觀察者,它只知道它們都有一個(gè)共同的接口。
由于被觀察者和觀察者沒有緊密地耦合在一起,因此它們可以屬于不同的抽象化層次。這種耦合性使得代碼的可讀性、維護(hù)性大大提高。
2.觀察者模式實(shí)現(xiàn)了動(dòng)態(tài)聯(lián)動(dòng);
由于觀察者模式對(duì)觀察者注冊(cè)實(shí)行管理,那就可以在運(yùn)行期間,通過動(dòng)態(tài)的控制注冊(cè)的觀察者來控制某個(gè)動(dòng)作的聯(lián)動(dòng)范圍,從而實(shí)現(xiàn)動(dòng)態(tài)聯(lián)動(dòng)。
3.觀察者模式支持廣播通信。
目標(biāo)發(fā)送通知給觀察者是面向所有注冊(cè)的觀察者,所以目標(biāo)每次通知的信息就要對(duì)所有注冊(cè)的觀察者進(jìn)行廣播,也可以在目標(biāo)上添加新的方法來限制廣播的范圍。
Django 中Siganl 機(jī)制的典型應(yīng)用是,框架為 Models 創(chuàng)建了 pre_save、post_save等與Model的某些方法調(diào)用相關(guān)聯(lián)的信號(hào),如pre_save 和 post_save 分別會(huì)在 Modle的save()方法的調(diào)用之前和之后通知觀察者,從而讓觀察者進(jìn)行一系列操作。
django signal的處理是同步的,勿用于處理大批量任務(wù)。
django signal對(duì)程序的解耦、代碼的復(fù)用及維護(hù)性有很大的幫助。
Signal 機(jī)制的實(shí)現(xiàn)方式
Siganl的源碼位于django dispatch包下,主要的代碼位于 dispatcher.py中。
在dispatcher中定義了Signal類,以及一個(gè)用于使用Python裝飾器的方式來連接信號(hào)以及信號(hào)接受者的方法receiver(signal,**kwargs)。
class Signal(object): """ Base class for all signals Internal attributes: receivers { receiverkey (id) : weakref(receiver) } """ def __init__(self, providing_args=None, use_caching=False): """ 創(chuàng)建一個(gè)新的Signal providing_args 參數(shù),指定這個(gè)Siganl 在發(fā)出事件(調(diào)用send方法)時(shí),可以提供給觀察者的信息參數(shù) 比如 post_save()會(huì)帶上 對(duì)應(yīng)的instance對(duì)象,以及update_fields等 """ self.receivers = [] if providing_args is None: providing_args = [] self.providing_args = set(providing_args) self.lock = threading.Lock() self.use_caching = use_caching # For convenience we create empty caches even if they are not used. # A note about caching: if use_caching is defined, then for each # distinct sender we cache the receivers that sender has in # 'sender_receivers_cache'. The cache is cleaned when .connect() or # .disconnect() is called and populated on send(). self.sender_receivers_cache = weakref.WeakKeyDictionary() if use_caching else {} self._dead_receivers = False def connect(self, receiver, sender=None, weak=True, dispatch_uid=None): from django.conf import settings if dispatch_uid: lookup_key = (dispatch_uid, _make_id(sender)) else: lookup_key = (_make_id(receiver), _make_id(sender)) if weak: ref = weakref.ref receiver_object = receiver # Check for bound methods # 構(gòu)造弱引用的的receiver if hasattr(receiver, '__self__') and hasattr(receiver, '__func__'): ref = WeakMethod receiver_object = receiver.__self__ if sys.version_info >= (3, 4): receiver = ref(receiver) weakref.finalize(receiver_object, self._remove_receiver) else: receiver = ref(receiver, self._remove_receiver) with self.lock: #clear掉 由于弱引用 已被垃圾回收期回收的receivers self._clear_dead_receivers() for r_key, _ in self.receivers: if r_key == lookup_key: break else: self.receivers.append((lookup_key, receiver)) self.sender_receivers_cache.clear() def disconnect(self, receiver=None, sender=None, weak=True, dispatch_uid=None): if dispatch_uid: lookup_key = (dispatch_uid, _make_id(sender)) else: lookup_key = (_make_id(receiver), _make_id(sender)) disconnected = False with self.lock: self._clear_dead_receivers() for index in range(len(self.receivers)): (r_key, _) = self.receivers[index] if r_key == lookup_key: disconnected = True del self.receivers[index] break self.sender_receivers_cache.clear() return disconnected def has_listeners(self, sender=None): return bool(self._live_receivers(sender)) def send(self, sender, **named): 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 def send_robust(self, sender, **named): responses = [] if not self.receivers or self.sender_receivers_cache.get(sender) is NO_RECEIVERS: return responses # Call each receiver with whatever arguments it can accept. # Return a list of tuple pairs [(receiver, response), ... ]. for receiver in self._live_receivers(sender): try: response = receiver(signal=self, sender=sender, **named) except Exception as err: if not hasattr(err, '__traceback__'): err.__traceback__ = sys.exc_info()[2] responses.append((receiver, err)) else: responses.append((receiver, response)) return responses def _clear_dead_receivers(self): # Note: caller is assumed to hold self.lock. if self._dead_receivers: self._dead_receivers = False new_receivers = [] for r in self.receivers: if isinstance(r[1], weakref.ReferenceType) and r[1]() is None: continue new_receivers.append(r) self.receivers = new_receivers def _live_receivers(self, sender): """ 過濾掉 已經(jīng)被 垃圾回收的receiver """ receivers = None # 如果使用了cache , 并且沒有調(diào)用過_remove_receiver 函數(shù) 則去 sender_receivers_cache中查找 if self.use_caching and not self._dead_receivers: receivers = self.sender_receivers_cache.get(sender) # We could end up here with NO_RECEIVERS even if we do check this case in # .send() prior to calling _live_receivers() due to concurrent .send() call. if receivers is NO_RECEIVERS: return [] if receivers is None: with self.lock: self._clear_dead_receivers() senderkey = _make_id(sender) receivers = [] for (receiverkey, r_senderkey), receiver in self.receivers: if r_senderkey == NONE_ID or r_senderkey == senderkey: receivers.append(receiver) if self.use_caching: if not receivers: self.sender_receivers_cache[sender] = NO_RECEIVERS else: # Note, we must cache the weakref versions. self.sender_receivers_cache[sender] = receivers non_weak_receivers = [] for receiver in receivers: if isinstance(receiver, weakref.ReferenceType): # Dereference the weak reference. receiver = receiver() if receiver is not None: non_weak_receivers.append(receiver) else: non_weak_receivers.append(receiver) return non_weak_receivers def _remove_receiver(self, receiver=None): self._dead_receivers = True
connect方法
connect方法用于連接信號(hào)和信號(hào)處理函數(shù),類似的概念相當(dāng)于為某個(gè)事件(信號(hào)發(fā)出表示一個(gè)事件)注冊(cè)觀察者(處理函數(shù)),函數(shù)參數(shù)中receiver就是信號(hào)處理函數(shù)(函數(shù)也是對(duì)象,這太方便了),sender表示信號(hào)的發(fā)送者,比如Django框架中的post_save()這個(gè)信號(hào),任何一個(gè)模型在save()函數(shù)調(diào)用之后都會(huì)發(fā)出這個(gè)信號(hào),但是我們只想關(guān)注某一個(gè)模型 save()方法調(diào)用的事件發(fā)生,就可以指定sender為我們需要關(guān)注的模型類。
weak參數(shù)表示是否將receiver轉(zhuǎn)換成弱引用對(duì)象,Siganl中默認(rèn)會(huì)將所有的receiver轉(zhuǎn)成弱引用,所以如果你的receiver是個(gè)局部對(duì)象的話,那么receiver可能會(huì)被垃圾回收期回收,receiver也就變成一個(gè)dead_receiver了,Siganl會(huì)在connect和disconnect方法調(diào)用的時(shí)候,清除dead_receiver。
dispatch_uid
,這個(gè)參數(shù)用于唯一標(biāo)識(shí)這個(gè)receiver函數(shù),主要的作用是防止receiver函數(shù)被注冊(cè)多次,這樣會(huì)導(dǎo)致receiver函數(shù)會(huì)執(zhí)行多次,這可能是我們不想要的一個(gè)結(jié)果。
disconnect方法
disconnect方法用于斷開信號(hào)的接收器,函數(shù)內(nèi)首先會(huì)生成根據(jù)sender和receiver對(duì)象構(gòu)造出的一個(gè)標(biāo)識(shí)lookup_key
,在遍歷receiver數(shù)組時(shí),根據(jù)lookup_key找到需要disconnect的receiver然后從數(shù)組中刪除這個(gè)receiver。
send和send_robust
send和send_robust方法都是用于發(fā)送事件的函數(shù),不同點(diǎn)在于send_robust函數(shù)中會(huì)捕獲信號(hào)接收函數(shù)發(fā)生的異常,添加到返回的responses數(shù)組中。
Siganl類的使用
Django signal的處理過程如下圖所示:
內(nèi)建signal的使用
模型相關(guān):
- pre_save 對(duì)象save前觸發(fā)
- post_save 對(duì)象save后觸發(fā)
- pre_delete 對(duì)象delete前觸發(fā)
- post_delete 對(duì)象delete后觸發(fā)
- m2m_changed ManyToManyField 字段更新后觸發(fā)
請(qǐng)求相關(guān):
- request_started 一個(gè)request請(qǐng)求前觸發(fā)
- request_finished request請(qǐng)求后觸發(fā)
針對(duì)django自帶的signal,我們只需要編寫receiver 即可,使用如下。
第一步,編寫receiver并綁定到signal
# myapp/signals/handlers.py from django.dispatch import receiver from django.core.signals import request_finished ## decorators 方式綁定 @receiver(request_finished, dispatch_uid="request_finished") def my_signal_handler(sender, **kwargs): print("Request finished!================================") # 普通綁定方式 def my_signal_handler(sender, **kwargs): print("Request finished!================================") request_finished.connect(my_signal_handler) ##################################################### # 針對(duì)model 的signal from django.dispatch import receiver from django.db.models.signals import post_save from polls.models import MyModel @receiver(post_save, sender=MyModel, dispatch_uid="mymodel_post_save") def my_model_handler(sender, **kwargs): print('Saved: {}'.format(kwargs['instance'].__dict__))
用dispatch_uid
確保此receiver只調(diào)用一次
第二步,加載signal
# myapp/__init__py default_app_config = 'myapp.apps.MySendingAppConfig'
# myapp/apps.py from django.apps import AppConfig class MyAppConfig(AppConfig): name = 'myapp' def ready(self): # signals are imported, so that they are defined and can be used import myapp.signals.handlers
到此,當(dāng)系統(tǒng)受到request 請(qǐng)求完成后,便會(huì)執(zhí)行receiver。
其他內(nèi)建的signal,參考官方文檔:
https://docs.djangoproject.com/en/1.9/topics/signals/
自定義signal的使用
自定義signal,需要我們編寫signal和receiver。
第一步,編寫signal
myapp.signals.signals.py importdjango.dispatch my_signal = django.dispatch.Signal(providing_args=["my_signal_arg1", "my_signal_arg_2"])
第二步,加載signal
# myapp/__init__py default_app_config = 'myapp.apps.MySendingAppConfig' myapp/apps.py from django.apps import AppConfig class MyAppConfig(AppConfig): name = 'myapp' def ready(self): # signals are imported, so that they are defined and can be used import myapp.signals.handlers
第三步,事件觸發(fā)時(shí),發(fā)送signal
# myapp/views.py from .signals.signals import my_signal my_signal.send(sender="some function or class", my_signal_arg1="something", my_signal_arg_2="something else"])
自定義的signal,django已經(jīng)為我們編寫了此處的事件監(jiān)聽。
第四步,收到signal,執(zhí)行receiver
# myapp/signals/handlers.py from django.dispatch import receiver from myapp.signals.signals import my_signal @receiver(my_signal, dispatch_uid="my_signal_receiver") def my_signal_handler(sender, **kwargs): print('my_signal received')
此時(shí),我們自定義的signal 便開發(fā)完成了。
總結(jié)
以上就是本文關(guān)于Django中的Signal代碼詳解的全部?jī)?nèi)容,希望對(duì)大家有所幫助。感興趣的朋友可以繼續(xù)參閱本站其他相關(guān)專題,如有不足之處,歡迎留言指出。感謝朋友們對(duì)本站的支持!
相關(guān)文章
使用Python進(jìn)行數(shù)獨(dú)求解詳解(二)
對(duì)于利用Python求解數(shù)獨(dú),我們可以采用回溯算法實(shí)現(xiàn)一個(gè)簡(jiǎn)單的版本。本文將此基礎(chǔ)上,通過改進(jìn)來提升數(shù)獨(dú)問題求解算法的性能。需要的可以參考一下2022-02-02教你從零開始實(shí)現(xiàn)貪吃蛇Python小游戲
這篇文章主要教你從零開始實(shí)現(xiàn)貪吃蛇Python小游戲,沒有使用pygame庫,附帶源碼和注釋,非常有意思,需要的朋友可以參考下2023-03-03django API 中接口的互相調(diào)用實(shí)例
這篇文章主要介紹了django API 中接口的互相調(diào)用實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-04-04python 讀取txt中每行數(shù)據(jù),并且保存到excel中的實(shí)例
下面小編就為大家分享一篇python 讀取txt中每行數(shù)據(jù),并且保存到excel中的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-04-04用Python的Tornado框架結(jié)合memcached頁面改善博客性能
這篇文章主要介紹了用Python的Tornado框架結(jié)合memcached頁面改善vLog性能,主要使用到了緩存來提升性能,需要的朋友可以參考下2015-04-04