Django中的Signal代碼詳解
本文研究的主要是Django開(kāi)發(fā)中的signal 的相關(guān)內(nèi)容,具體如下。
前言
在web開(kāi)發(fā)中, 你可能會(huì)遇到下面這種場(chǎng)景:
在用戶(hù)完成某個(gè)操作后, 自動(dòng)去執(zhí)行一些后續(xù)的操作. 譬如用戶(hù)完成修改密碼后,
你要發(fā)送一份確認(rèn)郵件.
當(dāng)然可以把邏輯寫(xiě)在一起,但是有個(gè)問(wèn)題是,觸發(fā)操作一般不止一種(如用戶(hù)更改了其它信息的確認(rèn)郵件),這時(shí)候這個(gè)邏輯會(huì)需要寫(xiě)多次,所以你可能會(huì)想著DRY(Don't repeat yourself),于是你把它寫(xiě)到了一個(gè)函數(shù)中,每次調(diào)用。當(dāng)然這是沒(méi)問(wèn)題的.
但是, 如果你換個(gè)思路你會(huì)發(fā)現(xiàn)另一個(gè)完全不同的方案, 即:
- 類(lèi)似于daemon的程序監(jiān)聽(tīng)著特定的事件
- 前置操作來(lái)觸發(fā)相應(yīng)的事件
- 監(jiān)聽(tīng)程序執(zhí)行對(duì)應(yīng)的操作
這樣的好處是什么呢?
- 松耦合(不用把后續(xù)操作寫(xiě)在主邏輯中)
- 便于復(fù)用(這也是為什么django本身,及第三方應(yīng)用如pinax大量使用此技術(shù)的原因),在各種高級(jí)語(yǔ)言中都會(huì)有類(lèi)似的特性,如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ā)出通知。這通常透過(guò)呼叫各觀察者所提供的方法來(lái)實(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ì)列、事件總線(xiàn)的處理機(jī)制。
優(yōu)點(diǎn)
1.解除耦合,讓耦合的雙方都依賴(lài)于抽象,從而使得各自的變換都不會(huì)影響另一邊的變換。
它在被觀察者和觀察者之間建立一個(gè)抽象的耦合。被觀察者角色所知道的只是一個(gè)具體觀察者列表,每一個(gè)具體觀察者都符合一個(gè)抽象觀察者的接口。被觀察者并不認(rèn)識(shí)任何一個(gè)具體觀察者,它只知道它們都有一個(gè)共同的接口。
由于被觀察者和觀察者沒(méi)有緊密地耦合在一起,因此它們可以屬于不同的抽象化層次。這種耦合性使得代碼的可讀性、維護(hù)性大大提高。
2.觀察者模式實(shí)現(xiàn)了動(dòng)態(tài)聯(lián)動(dòng);
由于觀察者模式對(duì)觀察者注冊(cè)實(shí)行管理,那就可以在運(yùn)行期間,通過(guò)動(dòng)態(tài)的控制注冊(cè)的觀察者來(lái)控制某個(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)上添加新的方法來(lái)限制廣播的范圍。
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類(lèi),以及一個(gè)用于使用Python裝飾器的方式來(lái)連接信號(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):
"""
過(guò)濾掉 已經(jīng)被 垃圾回收的receiver
"""
receivers = None
# 如果使用了cache , 并且沒(méi)有調(diào)用過(guò)_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ù),類(lèi)似的概念相當(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)注的模型類(lèi)。
weak參數(shù)表示是否將receiver轉(zhuǎn)換成弱引用對(duì)象,Siganl中默認(rèn)會(huì)將所有的receiver轉(zhuǎn)成弱引用,所以如果你的receiver是個(gè)局部對(duì)象的話(huà),那么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方法用于斷開(kāi)信號(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類(lèi)的使用
Django signal的處理過(guò)程如下圖所示:

內(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,我們只需要編寫(xiě)receiver 即可,使用如下。
第一步,編寫(xiě)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,需要我們編寫(xiě)signal和receiver。
第一步,編寫(xiě)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)為我們編寫(xiě)了此處的事件監(jiān)聽(tīng)。
第四步,收到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 便開(kāi)發(fā)完成了。
總結(jié)
以上就是本文關(guān)于Django中的Signal代碼詳解的全部?jī)?nèi)容,希望對(duì)大家有所幫助。感興趣的朋友可以繼續(xù)參閱本站其他相關(guān)專(zhuān)題,如有不足之處,歡迎留言指出。感謝朋友們對(duì)本站的支持!
相關(guān)文章
使用Python進(jìn)行數(shù)獨(dú)求解詳解(二)
對(duì)于利用Python求解數(shù)獨(dú),我們可以采用回溯算法實(shí)現(xiàn)一個(gè)簡(jiǎn)單的版本。本文將此基礎(chǔ)上,通過(guò)改進(jìn)來(lái)提升數(shù)獨(dú)問(wèn)題求解算法的性能。需要的可以參考一下2022-02-02
Python畫(huà)圖時(shí)如何調(diào)用本地字體
這篇文章主要為大家介紹在通過(guò)Python繪制圖畫(huà)時(shí)如何調(diào)用本地的字體,從而解決中文亂碼的問(wèn)題。感興趣的小伙伴快來(lái)跟隨小編學(xué)習(xí)學(xué)習(xí)吧2021-12-12
教你從零開(kāi)始實(shí)現(xiàn)貪吃蛇Python小游戲
這篇文章主要教你從零開(kāi)始實(shí)現(xiàn)貪吃蛇Python小游戲,沒(méi)有使用pygame庫(kù),附帶源碼和注釋,非常有意思,需要的朋友可以參考下2023-03-03
django API 中接口的互相調(diào)用實(shí)例
這篇文章主要介紹了django API 中接口的互相調(diào)用實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-04-04
python 讀取txt中每行數(shù)據(jù),并且保存到excel中的實(shí)例
下面小編就為大家分享一篇python 讀取txt中每行數(shù)據(jù),并且保存到excel中的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-04-04
用Python的Tornado框架結(jié)合memcached頁(yè)面改善博客性能
這篇文章主要介紹了用Python的Tornado框架結(jié)合memcached頁(yè)面改善vLog性能,主要使用到了緩存來(lái)提升性能,需要的朋友可以參考下2015-04-04

