您好,登錄后才能下訂單哦!
本文研究的主要是Django開發中的signal 的相關內容,具體如下。
在web開發中, 你可能會遇到下面這種場景:
在用戶完成某個操作后, 自動去執行一些后續的操作. 譬如用戶完成修改密碼后,
你要發送一份確認郵件.
當然可以把邏輯寫在一起,但是有個問題是,觸發操作一般不止一種(如用戶更改了其它信息的確認郵件),這時候這個邏輯會需要寫多次,所以你可能會想著DRY(Don't repeat yourself),于是你把它寫到了一個函數中,每次調用。當然這是沒問題的.
但是, 如果你換個思路你會發現另一個完全不同的方案, 即:
這樣的好處是什么呢?
Siganl是Django框架中提供的一個 “信號分發器”。它是設計模式中經常提到的觀察者模式的一個實現應用。
在此種模式中,一個目標物件管理所有相依于它的觀察者物件,并且在它本身的狀態改變時主動發出通知。這通常透過呼叫各觀察者所提供的方法來實現。
觀察者模式的使用場景
優點
1.解除耦合,讓耦合的雙方都依賴于抽象,從而使得各自的變換都不會影響另一邊的變換。
它在被觀察者和觀察者之間建立一個抽象的耦合。被觀察者角色所知道的只是一個具體觀察者列表,每一個具體觀察者都符合一個抽象觀察者的接口。被觀察者并不認識任何一個具體觀察者,它只知道它們都有一個共同的接口。
由于被觀察者和觀察者沒有緊密地耦合在一起,因此它們可以屬于不同的抽象化層次。這種耦合性使得代碼的可讀性、維護性大大提高。
2.觀察者模式實現了動態聯動;
由于觀察者模式對觀察者注冊實行管理,那就可以在運行期間,通過動態的控制注冊的觀察者來控制某個動作的聯動范圍,從而實現動態聯動。
3.觀察者模式支持廣播通信。
目標發送通知給觀察者是面向所有注冊的觀察者,所以目標每次通知的信息就要對所有注冊的觀察者進行廣播,也可以在目標上添加新的方法來限制廣播的范圍。
Django 中Siganl 機制的典型應用是,框架為 Models 創建了 pre_save、post_save等與Model的某些方法調用相關聯的信號,如pre_save 和 post_save 分別會在 Modle的save()方法的調用之前和之后通知觀察者,從而讓觀察者進行一系列操作。
django signal的處理是同步的,勿用于處理大批量任務。
django signal對程序的解耦、代碼的復用及維護性有很大的幫助。
Siganl的源碼位于django dispatch包下,主要的代碼位于 dispatcher.py中。
在dispatcher中定義了Signal類,以及一個用于使用Python裝飾器的方式來連接信號以及信號接受者的方法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): """ 創建一個新的Signal providing_args 參數,指定這個Siganl 在發出事件(調用send方法)時,可以提供給觀察者的信息參數 比如 post_save()會帶上 對應的instance對象,以及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 # 構造弱引用的的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): """ 過濾掉 已經被 垃圾回收的receiver """ receivers = None # 如果使用了cache , 并且沒有調用過_remove_receiver 函數 則去 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方法用于連接信號和信號處理函數,類似的概念相當于為某個事件(信號發出表示一個事件)注冊觀察者(處理函數),函數參數中receiver就是信號處理函數(函數也是對象,這太方便了),sender表示信號的發送者,比如Django框架中的post_save()這個信號,任何一個模型在save()函數調用之后都會發出這個信號,但是我們只想關注某一個模型 save()方法調用的事件發生,就可以指定sender為我們需要關注的模型類。
weak參數表示是否將receiver轉換成弱引用對象,Siganl中默認會將所有的receiver轉成弱引用,所以如果你的receiver是個局部對象的話,那么receiver可能會被垃圾回收期回收,receiver也就變成一個dead_receiver了,Siganl會在connect和disconnect方法調用的時候,清除dead_receiver。
dispatch_uid
,這個參數用于唯一標識這個receiver函數,主要的作用是防止receiver函數被注冊多次,這樣會導致receiver函數會執行多次,這可能是我們不想要的一個結果。
disconnect方法
disconnect方法用于斷開信號的接收器,函數內首先會生成根據sender和receiver對象構造出的一個標識lookup_key
,在遍歷receiver數組時,根據lookup_key找到需要disconnect的receiver然后從數組中刪除這個receiver。
send和send_robust
send和send_robust方法都是用于發送事件的函數,不同點在于send_robust函數中會捕獲信號接收函數發生的異常,添加到返回的responses數組中。
Django signal的處理過程如下圖所示:
模型相關:
請求相關:
針對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) ##################################################### # 針對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只調用一次
第二步,加載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
到此,當系統受到request 請求完成后,便會執行receiver。
其他內建的signal,參考官方文檔:
https://docs.djangoproject.com/en/1.9/topics/signals/
自定義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
第三步,事件觸發時,發送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已經為我們編寫了此處的事件監聽。
第四步,收到signal,執行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')
此時,我們自定義的signal 便開發完成了。
以上就是本文關于Django中的Signal代碼詳解的全部內容,希望對大家有所幫助。感興趣的朋友可以繼續參閱本站其他相關專題,如有不足之處,歡迎留言指出。感謝朋友們對本站的支持!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。