シグナル

revision-up-to:17812 (1.4)

Django には「シグナルディスパッチャ (signal dispatcher)」が組み込まれていま す。シグナルディスパッチャにより、アプリケーションをフレームワークから脱カッ プリングしつつ、フレームワークのどこかで起きたアクションに応じた通知を受け られます。簡単にいえば、シグナルによって、ある「 送信側(sender) 」から 複数の「 受信側(receiver) 」に向けて、何らかのアクションが起きたことを通 知できるのです。シグナルは、たくさんのコードが同じイベントを待ち受けるよう な状況で特に便利です。

Django は、自分自身の特定のアクションを通知するための 組み込みシグナ ル を提供しています。組み込みシグナルの中には、以下のような 便利なものがあります:

組み込みシグナルの全容と各々の解説は、 組み込みシグナルのドキュメント で解説しています。

シグナルは 自分で定義したり送信したり できます。詳しくは以下を参照し てください。

シグナルを待ち受ける

シグナルを受信するには、シグナルが送信されたときに呼び出される レシーバ (receiver) 関数を、 Signal.connect() メソッドを使って登録する必要が あります。

Signal.connect(receiver[, sender=None, weak=True, dispatch_uid=None])
Parameters:
  • receiver – シグナルに結びつけられるコールバック関数を指定します。 詳細は レシーバ関数 を参照してください。
  • sender – シグナルを受け取るための特定のセンダを指定します。詳細は 特定のセンダから送信されたシグナルだけを結びつける を参照してください。
  • weak – デフォルトで Django は、シグナルハンドラを弱参照として保持 します。なのでレシーバがローカル関数の場合、ガベージコレクトされる恐れが あります。これを防ぐにはシグナルの connect() メソッドを呼び出す際に weak=False を渡してください。
  • dispatch_uid – ユニークな識別子。シグナルのレシーバに重複するシグナル が送られる場合に備えたものです。詳細は 重複するシグナルを防ぐ を参照してください。

HTTP リクエストの処理が終ったときに呼び出されるシグナルのレシーバを登録して、 この仕組みを見てみましょう。この例では、 request_finished シグナルをレシーバに結びつけます。

レシーバ関数

まず、レシーバ関数を定義します。レシーバは通常の Python の関数やメソッドと して定義します:

def my_callback(sender, **kwargs):
    print "Request finished!"

この関数は固定引数の sender と、ワイルドカードで表現された任意のキーワー ド引数 (**kwargs) をとります。シグナルハンドラは、全てこの形式の引数を とらねばなりません。

sender については 後で 説明するとして、今はまず **kwargs に注目しましょう。シグナルはすべてキーワード引数を伴い、キーワー ド引数の内容はいつでも変更される可能性があります。 request_finished の場合、ドキュメントにキーワー ド引数をもたないと明記されているので、シグナルハンドラを my_callback(sender) と書いていいと思いがちです。

しかしこれは誤っています。実際、 my_callback(sender) のように定義すると Django はエラーを送出します。というのも、将来シグナルに引数が追加されるかも しれず、そのときにもレシーバは新たに追加された引数を扱えねばならないからで す。

レシーバ関数を結びつける

レシーバをシグナルに結びつけるには 2 つの方法があります。手動での接続方法は 以下のようになります:

from django.core.signals import request_finished

request_finished.connect(my_callback)

あるいは、 receiver デコレータを使う方法があります。レシーバを定義するときに 使います:

from django.core.signals import request_finished
from django.dispatch import receiver

@receiver(request_finished)
def my_callback(sender, **kwargs):
    print "Request finished!"

これで、 my_callback 関数は、リクエストの処理が終了するたびに呼び出され ます。

リリースノートを参照してください

receiver デコレータは Django 1.3 で追加されました。

シグナル処理のコードはどこにおけばよいのですか?

シグナルの処理や登録のためのコードは、どこでも好きな場所に置けます。 とはいえ、自分の登録したいシグナルが送信されるよりも前に、コードの入っ ているモジュールを早々に import しておきたいいでしょう。そのため、シグ ナルハンドラの登録は models.py に置くのがよいでしょう。

特定のセンダから送信されたシグナルだけを結びつける

シグナルには何度も送信されるものがありますが、その中でも特定のサブセットだ けを受け取りたい場合もあるでしょう。例えば、モデルインスタンスが保存される ときに送信される django.db.models.signals.pre_save を考えましょう。 大抵は、シグナルを受信したいのは すべての モデルの保存時ではなく、 特定の モデルの保存時のはずです。

こうしたケースのために、特定のセンダから送られるシグナルに対してレシーバを 登録できます。 django.db.models.signals.pre_save の場合、センダは インスタンスを保存しようとするモデルなので、以下のようにして、特定のモデル に対してのみシグナルを受信させられます:

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):
    ...

これで、保存されるインスタンスが MyModel のインスタンスであるときだけ、 my_handler 関数が呼び出されます。

センダに入るオブジェクトは、シグナルによって異なります。個々のシグナルにつ いての情報は 組み込みシグナルのドキュメント を参照して ください。

重複するシグナルを防ぐ

いくつかの状況では、シグナルに結びつけたモジュールが複数回インポートされます。 これはレシーバ関数が 2 回以上登録されている可能性があり、 1 つのシグナルイベント が複数回呼び出されています。

この振る舞いに問題があるとき (例えばモデルが保存されたときにメールを送るシグナル を使うなど) は、ユニークな識別子である dispatch_uid 引数をレシーバ関数に 渡してください。普通この識別子は文字列ですが、ハッシュ可能オブジェクトも使え ます。個々のユニークな dispatch_uid 値のため、レシーバ関数はシグナルに 1 度 だけ結び付けられます。

from django.core.signals import request_finished

request_finished.connect(my_callback, dispatch_uid="my_unique_identifier")

シグナルの定義と送信

自分のアプリケーション内でも、シグナルのインフラストラクチャを使ったり、独 自のシグナルを提供したりできます。

シグナルを定義する

class Signal([providing_args=list])

シグナルは全て django.dispatch.Signal のインスタンスです。 providing_args は、シグナルがリスナに対して提供する引数の名前が入ったリ ストです。

例えば:

import django.dispatch

pizza_done = django.dispatch.Signal(providing_args=["toppings", "size"])

上のコードは pizza_done シグナルを作成し、このシグナルが toppingsize という引数をもたらすことを宣言しています。

このリストはいつでも変更できるので、最初の時点で正確に API を定義しておく必 要はありません。

シグナルを送信する

Django にはシグナルを送信する方法が 2 つあります。

Signal.send(sender, **kwargs)
Signal.send_robust(sender, **kwargs)

シグナルを送信するには Signal.send()Signal.send_robust() を 呼び出します。 sender を必ず指定し、必要に応じて追加の引数を指定します。

例えば、 pizza_done シグナルを送信するには、以下のように書きます:

class PizzaStore(object):
    ...

    def send_pizza(self, toppings, size):
        pizza_done.send(sender=self, toppings=toppings, size=size)
        ...

send()send_robust() の両方とも、要素数 2 のタプルのリスト [(receiver, response), ... ] を返します。呼び出されたレシーバ関数とその レスポンス値が、リストになっています。

send()send_robust() の違いは、レシーバ関数から送出された例外をどの ように処理するかです。 send() はレシーバから送出される例外を一切 捕らえま せん 。単純にエラーが伝播されます。レシーバがエラーを起こすと、シグナルは通知 されません。

send_robust() は Python の Exception クラスから送出されるエラーを捕ら えます。シグナルがレシーバに通知されることを保証します。レシーバがエラーを送出 するとエラーインスタンス (そのレシーバとエラー内容のタプル) が返されます。

シグナルとの接続を断つ

Signal.disconnect([receiver=None, sender=None, weak=True, dispatch_uid=None])

シグナルとレシーバの接続を断つには Signal.disconnect() を呼び出します。 引数は Signal.connect() に述べられている通りです。

receiver 引数には、接続を断ちたい登録済みのレシーバを指定します。 レシーバを識別するために dispath_uid 使われている場合、 receiverNone で構いません。