#Djangoのシグナルについて
##はじめに
Djangoで特定のイベント発生時にシグナルを受信することができます。
HTTPリクエストを送信する時に呼ばれるrequest_started
や、HTTPリクエストの処理が終了した時に呼ばれるrequest_finished
がそれにあたります。
##Receiver関数の登録
レシーバ関数を登録する方法は2通り
・Signal.connect
・receiverデコレータ
##connect
Django1.7.1
connect(self, receiver, sender=None, weak=True, dispatch_uid=None)
Django1.7.1のconnect関数になります。
各パラメータは、
######receiver
function or instance method
シグナル受信時に実行されるオブジェクトになります。
######sender
特定のsenderからのみシグナルを受信する場合に設定します。
######weak
ここがTrueならreceiverが弱い参照で保持されます。
######dispatch_uid
特定のシグナルに対して重複したモジュールを登録する場合に設定します。
設定する場合は、文字列を指定すれば問題ないです。
Djangoのauthモジュールの例
#user loginのシグナル定義
#django.contrib.auth.signals
user_logged_in = Signal(providing_args=['request', 'user'])
#receiverファンクション
def login_callback(sender, **kwargs):
#Signal定義のproviding_argsに定義されているオブジェクトを取得できる
request = kwargs['request']
user = kwargs['user']
#connect
user_logged_in.connect(login_callback)
これでログイン後にlogin_callbackが呼ばれます。
実際にuser_logged_inがシグナル送信している箇所をみてみます。
##django.contrib.auth.__init__
request.session[SESSION_KEY] = user.pk
request.session[BACKEND_SESSION_KEY] = user.backend
request.session[HASH_SESSION_KEY] = session_auth_hash
if hasattr(request, 'user'):
request.user = user
rotate_token(request)
user_logged_in.send(sender=user.__class__, request=request, user=user)
sendメソッドでsender、request,userを設定しているのがわかります。
##receiverデコレータ
レシーバ関数を登録するもう一つの方法は、receiverデコレータを使う方法です。
@receiver(signal, sender=myModel)
def test_callback(sender, **kwargs):
pass
こんな感じで定義します。
receiverの中を見てみます。
#django.dispatch.dispatcher
def receiver(signal, **kwargs):
def _decorator(func):
if isinstance(signal, (list, tuple)):
for s in signal:
s.connect(func, **kwargs)
else:
signal.connect(func, **kwargs)
return func
return _decorator
見てわかる通りconnectを呼んでいます。
複数のシグナルを登録することも可能です。
######デコレータ
補足としてreceiverデコレータの挙動をこの自作receiverを参考に見てみます。
#decodeco.py
def receiver(signal, **kwargs):
def _deco(func): #func=test
print('receiver %s' % kwargs['sender'])
print(signal) #signal=sample
print(func)
return func
return _deco
def sample():
print('sample')
@receiver(sample, sender='sender_mymodel')
def test(sender, **kwargs):
print('test')
if __name__ == '__main__':
test(1)
これを実行してみます。
$ python3 decodeco.py
receiver sender_mymodel
<function sample at 0x7f63129f3050>
<function test at 0x7f63129f3170>
test
デコレータで展開さているのが少しわかると思います。
実際にconnectで登録したuser_logged_inをreceiverに変更してみます。
@receiver(user_logged_in, sender=myModel)
def login_callback(sender, **kwargs):
request = kwargs['request']
user = kwargs['user']
さらにこのreceiverをわかりやすくしてみます。
#django.dispatch.dispatcher.receiverをlogin_callbackで展開した
def receiver(user_logged_in, {'sender': myModel}):
def _decorator(login_callback):
user_logged_in.connect(login_callback, {'sender': myModel})
return login_callback
return _decorator
簡単に書くとこんな感じでしょうか。
connect、receiverで登録するのもどちらも同じ様なものなので、用途によって使い分けるといいと思います。
##最後に
connectの中を見てみます。
#django.dispatch.dispatcher
#長いので、途中省略してます
def connect(self, receiver, 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))
if weak:
ref = weakref.ref
receiver_object = receiver
# Check for bound methods
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:
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()
connectの中を見ると、弱参照の話も必要ですが長くなるので今回は省略します。
最後の***self.receivers.append((lookup_key, recever))***でレシーバを登録しているのがわかると思います。
これでレシーバ登録までの流れは終わりです。
機会があればsendや***_live_receivers***もまとめられればいいなと思います。
間違っている箇所等ありましたら指摘いただければ助かります。