========= シグナル ========= :revision-up-to: 17812 (1.4) .. module:: django.dispatch :synopsis: Signal dispatch Django には「シグナルディスパッチャ (signal dispatcher)」が組み込まれていま す。シグナルディスパッチャにより、アプリケーションをフレームワークから脱カッ プリングしつつ、フレームワークのどこかで起きたアクションに応じた通知を受け られます。簡単にいえば、シグナルによって、ある「 *送信側(sender)* 」から 複数の「 *受信側(receiver)* 」に向けて、何らかのアクションが起きたことを通 知できるのです。シグナルは、たくさんのコードが同じイベントを待ち受けるよう な状況で特に便利です。 Django は、自分自身の特定のアクションを通知するための :doc:`組み込みシグナ ル` を提供しています。組み込みシグナルの中には、以下のような 便利なものがあります: * :data:`django.db.models.signals.pre_save` および :data:`django.db.models.signals.post_save` モデルの :meth:`~django.db.models.Model.save` メソッド呼び出しの前後 で送信されます。 * :data:`django.db.models.signals.pre_delete` および :data:`django.db.models.signals.post_delete` モデルの :meth:`~django.db.models.Model.delete` メソッドか、クエリセットの :meth:`~django.db.models.query.QuerySet.delete` 呼び出しの前後に送信されます。 * :data:`django.db.models.signals.m2m_changed` :class:`ManyToManyField` が変更されたときに送信されます。 * :data:`django.core.signals.request_started` および :data:`django.core.signals.request_finished` Django が HTTP リクエストを処理する直前直後に送信されます。 組み込みシグナルの全容と各々の解説は、 :doc:`組み込みシグナルのドキュメント ` で解説しています。 シグナルは `自分で定義したり送信したり`_ できます。詳しくは以下を参照し てください。 .. _自分で定義したり送信したり: `シグナルの定義と送信`_ シグナルを待ち受ける ==================== シグナルを受信するには、シグナルが送信されたときに呼び出される *レシーバ (receiver)* 関数を、 :meth:`.Signal.connect` メソッドを使って登録する必要が あります。 .. method:: Signal.connect(receiver, [sender=None, weak=True, dispatch_uid=None]) :param receiver: シグナルに結びつけられるコールバック関数を指定します。 詳細は :ref:`receiver-functions` を参照してください。 :param sender: シグナルを受け取るための特定のセンダを指定します。詳細は :ref:`connecting-to-specific-signals` を参照してください。 :param weak: デフォルトで Django は、シグナルハンドラを弱参照として保持 します。なのでレシーバがローカル関数の場合、ガベージコレクトされる恐れが あります。これを防ぐにはシグナルの ``connect()`` メソッドを呼び出す際に ``weak=False`` を渡してください。 :param dispatch_uid: ユニークな識別子。シグナルのレシーバに重複するシグナル が送られる場合に備えたものです。詳細は :ref:`preventing-duplicate-signals` を参照してください。 HTTP リクエストの処理が終ったときに呼び出されるシグナルのレシーバを登録して、 この仕組みを見てみましょう。この例では、 :data:`~django.core.signals.request_finished` シグナルをレシーバに結びつけます。 .. _receiver-functions: レシーバ関数 ------------- まず、レシーバ関数を定義します。レシーバは通常の Python の関数やメソッドと して定義します: .. code-block:: python def my_callback(sender, **kwargs): print "Request finished!" この関数は固定引数の ``sender`` と、ワイルドカードで表現された任意のキーワー ド引数 (``**kwargs``) をとります。シグナルハンドラは、全てこの形式の引数を とらねばなりません。 ``sender`` については `後で`_ 説明するとして、今はまず ``**kwargs`` に注目しましょう。シグナルはすべてキーワード引数を伴い、キーワー ド引数の内容はいつでも変更される可能性があります。 :data:`~django.core.signals.request_finished` の場合、ドキュメントにキーワー ド引数をもたないと明記されているので、シグナルハンドラを ``my_callback(sender)`` と書いていいと思いがちです。 .. _後で: `特定のセンダから送信されたシグナルだけを結びつける`_ しかしこれは誤っています。実際、 ``my_callback(sender)`` のように定義すると Django はエラーを送出します。というのも、将来シグナルに引数が追加されるかも しれず、そのときにもレシーバは新たに追加された引数を扱えねばならないからで す。 .. _connecting-receiver-functions: レシーバ関数を結びつける ------------------------ レシーバをシグナルに結びつけるには 2 つの方法があります。手動での接続方法は 以下のようになります: .. code-block:: python from django.core.signals import request_finished request_finished.connect(my_callback) あるいは、 ``receiver`` デコレータを使う方法があります。レシーバを定義するときに 使います: .. code-block:: python 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`` 関数は、リクエストの処理が終了するたびに呼び出され ます。 .. versionadded:: 1.3 ``receiver`` デコレータは Django 1.3 で追加されました。 .. admonition:: シグナル処理のコードはどこにおけばよいのですか? シグナルの処理や登録のためのコードは、どこでも好きな場所に置けます。 とはいえ、自分の登録したいシグナルが送信されるよりも前に、コードの入っ ているモジュールを早々に import しておきたいいでしょう。そのため、シグ ナルハンドラの登録は ``models.py`` に置くのがよいでしょう。 .. _connecting-to-specific-signals: 特定のセンダから送信されたシグナルだけを結びつける ---------------------------------------------------- シグナルには何度も送信されるものがありますが、その中でも特定のサブセットだ けを受け取りたい場合もあるでしょう。例えば、モデルインスタンスが保存される ときに送信される :data:`django.db.models.signals.pre_save` を考えましょう。 大抵は、シグナルを受信したいのは *すべての* モデルの保存時ではなく、 *特定の* モデルの保存時のはずです。 こうしたケースのために、特定のセンダから送られるシグナルに対してレシーバを 登録できます。 :data:`django.db.models.signals.pre_save` の場合、センダは インスタンスを保存しようとするモデルなので、以下のようにして、特定のモデル に対してのみシグナルを受信させられます: .. code-block:: python 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`` 関数が呼び出されます。 センダに入るオブジェクトは、シグナルによって異なります。個々のシグナルにつ いての情報は :doc:`組み込みシグナルのドキュメント ` を参照して ください。 .. _preventing-duplicate-signals: 重複するシグナルを防ぐ ---------------------- いくつかの状況では、シグナルに結びつけたモジュールが複数回インポートされます。 これはレシーバ関数が 2 回以上登録されている可能性があり、 1 つのシグナルイベント が複数回呼び出されています。 この振る舞いに問題があるとき (例えばモデルが保存されたときにメールを送るシグナル を使うなど) は、ユニークな識別子である ``dispatch_uid`` 引数をレシーバ関数に 渡してください。普通この識別子は文字列ですが、ハッシュ可能オブジェクトも使え ます。個々のユニークな ``dispatch_uid`` 値のため、レシーバ関数はシグナルに 1 度 だけ結び付けられます。 .. code-block:: python from django.core.signals import request_finished request_finished.connect(my_callback, dispatch_uid="my_unique_identifier") シグナルの定義と送信 ===================== 自分のアプリケーション内でも、シグナルのインフラストラクチャを使ったり、独 自のシグナルを提供したりできます。 シグナルを定義する -------------------- .. class:: Signal([providing_args=list]) シグナルは全て :class:`django.dispatch.Signal` のインスタンスです。 ``providing_args`` は、シグナルがリスナに対して提供する引数の名前が入ったリ ストです。 例えば: .. code-block:: python import django.dispatch pizza_done = django.dispatch.Signal(providing_args=["toppings", "size"]) 上のコードは ``pizza_done`` シグナルを作成し、このシグナルが ``topping`` と ``size`` という引数をもたらすことを宣言しています。 このリストはいつでも変更できるので、最初の時点で正確に API を定義しておく必 要はありません。 シグナルを送信する -------------------- Django にはシグナルを送信する方法が 2 つあります。 .. method:: Signal.send(sender, **kwargs) .. method:: Signal.send_robust(sender, **kwargs) シグナルを送信するには :meth:`Signal.send` か :meth:`Signal.send_robust` を 呼び出します。 ``sender`` を必ず指定し、必要に応じて追加の引数を指定します。 例えば、 ``pizza_done`` シグナルを送信するには、以下のように書きます: .. code-block:: python 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`` クラスから送出されるエラーを捕ら えます。シグナルがレシーバに通知されることを保証します。レシーバがエラーを送出 するとエラーインスタンス (そのレシーバとエラー内容のタプル) が返されます。 シグナルとの接続を断つ ====================== .. method:: Signal.disconnect([receiver=None, sender=None, weak=True, dispatch_uid=None]) シグナルとレシーバの接続を断つには :meth:`Signal.disconnect` を呼び出します。 引数は :meth:`.Signal.connect` に述べられている通りです。 *receiver* 引数には、接続を断ちたい登録済みのレシーバを指定します。 レシーバを識別するために ``dispath_uid`` 使われている場合、 *receiver* は ``None`` で構いません。