信号
Djangoには、フレームワークの他の場所でアクションが発生したときに分離されたアプリケーションに通知を受け取るのに役立つ「シグナルディスパッチャー」が含まれています。 簡単に言うと、シグナルにより、特定の送信者は、受信者のセットに何らかのアクションが発生したことを通知できます。 これらは、多くのコードが同じイベントに関心を持つ可能性がある場合に特に役立ちます。
Djangoは、組み込みシグナルのセットを提供します。これにより、ユーザーコードはDjango自体から特定のアクションについて通知を受けることができます。 これらには、いくつかの便利な通知が含まれます。
django.db.models.signals.pre_save & django.db.models.signals.post_save
モデルの save()メソッドが呼び出される前または後に送信されます。
django.db.models.signals.pre_delete & django.db.models.signals.post_delete
モデルの delete()メソッドまたはクエリセットの delete()メソッドが呼び出される前または後に送信されます。
django.db.models.signals.m2m_changed
モデルの ManyToManyField が変更されたときに送信されます。
django.core.signals.request_started & django.core.signals.request_finished
DjangoがHTTPリクエストを開始または終了したときに送信されます。
完全なリストと各信号の完全な説明については、組み込みの信号ドキュメントを参照してください。
独自のカスタム信号を定義して送信することもできます。 下記参照。
信号を聞く
信号を受信するには、 Signal.connect()メソッドを使用してレシーバー関数を登録します。 信号が送信されると、レシーバー関数が呼び出されます。 信号のすべてのレシーバー機能は、登録された順序で一度に1つずつ呼び出されます。
- Signal.connect(receiver, sender=None, weak=True, dispatch_uid=None)
- ;; パラメーター
- ;;* レシーバー –このシグナルに接続されるコールバック関数。 詳細については、受信機機能を参照してください。
- sender –信号を受信する特定の送信者を指定します。 詳細については、特定の送信者から送信された信号への接続を参照してください。
- weak – Djangoは、デフォルトで弱参照としてシグナルハンドラーを保存します。 したがって、レシーバーがローカル関数である場合、ガベージコレクションされる可能性があります。 これを防ぐには、シグナルの
connect()
メソッドを呼び出すときにweak=False
を渡します。 - dispatch_uid –重複した信号が送信される可能性がある場合の信号受信機の一意の識別子。 詳細については、重複信号の防止を参照してください。
各HTTPリクエストが終了した後に呼び出されるシグナルを登録することにより、これがどのように機能するかを見てみましょう。 request_finished 信号に接続します。
レシーバー機能
まず、レシーバー関数を定義する必要があります。 レシーバーは、任意のPython関数またはメソッドにすることができます。
def my_callback(sender, **kwargs):
print("Request finished!")
この関数は、ワイルドカードキーワード引数(**kwargs
)とともにsender
引数を取ることに注意してください。 すべてのシグナルハンドラはこれらの引数を取る必要があります。
送信者については少し後でを見ていきますが、今は**kwargs
引数を見てください。 すべてのシグナルはキーワード引数を送信し、それらのキーワード引数はいつでも変更できます。 request_finished の場合、引数を送信しないと文書化されています。つまり、シグナル処理をmy_callback(sender)
として記述したくなるかもしれません。
これは間違っているでしょう-実際、そうすると、Djangoはエラーをスローします。 これは、いつでも引数が信号に追加される可能性があり、レシーバーがそれらの新しい引数を処理できる必要があるためです。
受信機機能の接続
受信機を信号に接続する方法は2つあります。 手動接続ルートを取ることができます:
from django.core.signals import request_finished
request_finished.connect(my_callback)
または、 receiveer()デコレータを使用することもできます。
- receiver(signal)
- ;; パラメーター
- signal –関数を接続するシグナルまたはシグナルのリスト。
デコレータとの接続方法は次のとおりです。
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
関数が呼び出されます。
このコードはどこにあるべきですか?
厳密に言えば、信号処理と登録コードは好きな場所に配置できますが、コードのインポートによる副作用を最小限に抑えるために、アプリケーションのルートモジュールとそのmodels
モジュールを避けることをお勧めします。
実際には、シグナルハンドラーは通常、関連するアプリケーションのsignals
サブモジュールで定義されます。 シグナルレシーバーは、アプリケーション構成クラスの ready()メソッドで接続されます。 receiver()デコレータを使用している場合は、 ready()内のsignals
サブモジュールをインポートします。
特定の送信者から送信された信号に接続する
一部のシグナルは何度も送信されますが、関心があるのはそれらのシグナルの特定のサブセットのみです。 たとえば、モデルが保存される前に送信される django.db.models.signals.pre_save シグナルについて考えてみます。 ほとんどの場合、任意のモデルがいつ保存されるかを知る必要はありません。1つの固有のモデルがいつ保存されるかだけです。
このような場合、特定の送信者によってのみ送信された信号を受信するように登録できます。 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):
...
my_handler
関数は、MyModel
のインスタンスが保存されている場合にのみ呼び出されます。
異なる信号は、送信者として異なるオブジェクトを使用します。 特定の各信号の詳細については、組み込みの信号ドキュメントを参照する必要があります。
重複信号の防止
状況によっては、受信機を信号に接続するコードが複数回実行される場合があります。 これにより、レシーバー関数が複数回登録され、シグナルイベントに対して何度も呼び出される可能性があります。 たとえば、 ready()メソッドは、テスト中に複数回実行される場合があります。 より一般的には、シグナル登録はインポートされた回数だけ実行されるため、これはプロジェクトがシグナルを定義するモジュールをインポートするすべての場所で発生します。
この動作に問題がある場合(モデルが保存されるたびにシグナルを使用して電子メールを送信する場合など)、レシーバー関数を識別するためにdispatch_uid
引数として一意の識別子を渡します。 この識別子は通常文字列ですが、ハッシュ可能なオブジェクトであれば十分です。 最終的な結果として、レシーバー関数は、一意のdispatch_uid
値ごとに1回だけ信号にバインドされます。
from django.core.signals import request_finished
request_finished.connect(my_callback, dispatch_uid="my_unique_identifier")
シグナルの定義と送信
アプリケーションは、シグナルインフラストラクチャを利用して、独自のシグナルを提供できます。
カスタム信号を使用する場合
シグナルは暗黙の関数呼び出しであり、デバッグを困難にします。 カスタムシグナルの送信者と受信者の両方がプロジェクト内にある場合は、明示的な関数呼び出しを使用することをお勧めします。
シグナルの定義
- class Signal
すべてのシグナルは django.dispatch.Signal インスタンスです。
例えば:
import django.dispatch
pizza_done = django.dispatch.Signal()
これにより、pizza_done
信号が宣言されます。
信号の送信
Djangoでシグナルを送信する方法は2つあります。
- Signal.send(sender, **kwargs)
- Signal.send_robust(sender, **kwargs)
シグナルを送信するには、 Signal.send()(すべての組み込みシグナルがこれを使用します)または Signal.send_robust()を呼び出します。 sender
引数(ほとんどの場合クラス)を指定する必要があり、他のキーワード引数をいくつでも指定できます。
たとえば、pizza_done
信号の送信は次のようになります。
class PizzaStore:
...
def send_pizza(self, toppings, size):
pizza_done.send(sender=self.__class__, toppings=toppings, size=size)
...
send()
とsend_robust()
はどちらも、呼び出されたレシーバー関数とその応答値のリストを表すタプルペア[(receiver, response), ... ]
のリストを返します。
send()
は、send_robust()
とは、レシーバー関数によって発生した例外の処理方法が異なります。 send()
は、レシーバーによって発生した例外をキャッチしません。 単にエラーを伝播させるだけです。 したがって、エラーが発生した場合に、すべての受信者に信号が通知されるとは限りません。
send_robust()
は、PythonのException
クラスから派生したすべてのエラーをキャッチし、すべての受信者にシグナルが通知されるようにします。 エラーが発生した場合、エラーインスタンスは、エラーを発生させたレシーバーのタプルペアで返されます。
トレースバックは、send_robust()
の呼び出し時に返されるエラーの__traceback__
属性に存在します。
信号の切断
- Signal.disconnect(receiver=None, sender=None, dispatch_uid=None)
受信機を信号から切断するには、 Signal.disconnect()を呼び出します。 引数は、 Signal.connect()で説明されているとおりです。 このメソッドは、レシーバーが切断されている場合はTrue
を返し、切断されていない場合はFalse
を返します。
receiver
引数は、切断する登録済みレシーバーを示します。 dispatch_uid
を使用して受信者を識別する場合は、None
の可能性があります。