シグナル¶
Django には "シグナルディスパッチャ" があり、フレームワークの他の場所でアクションが発生したときに、ほかのアプリケーションが通知を受けるのを助けてくれます。簡単に言うと、シグナルは特定の 送り手 が、あるアクションが発生したことを一連の 受け手 に通知できるようにします。特に、多くのコードが同じイベントに関連している場合に便利です。
例えば、サードパーティのアプリを登録して、設定変更の通知を受けることができます:
from django.apps import AppConfig
from django.core.signals import setting_changed
def my_receiver(sender, **kwargs):
print("Setting changed!")
class MyAppConfig(AppConfig):
...
def ready(self):
setting_changed.connect(my_receiver)
Django の 組み込みのシグナル は、ユーザコードに特定のアクションを通知します。
また、独自のカスタムシグナルを定義して送信することもできます。以下の シグナルの定義と送信 を参照してください。
警告
シグナルは疎結合のように見えますが、すぐに理解や調整、デバッグが難しいコードにつながります。
可能であれば、シグナルでディスパッチするのではなく、処理コードを直接呼び出すことを選ぶべきです。
シグナルを待ち受ける¶
シグナルを受信するには、 Signal.connect() メソッドを使って receiver 関数を登録します。シグナルが送信されると、レシーバ関数が呼び出されます。シグナルのすべてのレシーバ関数は、登録された順番に1つずつ呼び出されます。
- Signal.connect(receiver, sender=None, weak=True, dispatch_uid=None)[ソース]¶
- パラメータ:
receiver -- このシグナルに接続されるコールバック関数です。詳しくは レシーバ関数 を参照してください。
sender -- シグナルを受信する送信者を指定します。詳しくは 特定の送信者によって送られたシグナルに接続する を参照してください。
weak -- Django stores signal receivers as weak references by default. Thus, if your receiver is a local function, it may be garbage collected. To prevent this, pass
weak=Falsewhen you call the signal'sconnect()method.dispatch_uid -- シグナルが重複して送信される可能性がある場合の、シグナル受信機の一意な識別子。詳しくは 重複したシグナルを防止する を参照してください。
HTTP リクエストが終了するたびに呼び出されるシグナルを登録することで、この仕組みを見てみましょう。ここでは request_finished シグナルに接続します。
レシーバ関数¶
まず、レシーバ関数を定義する必要があります。レシーバはPythonの関数やメソッドであれば何でもかまいません:
def my_receiver(sender, **kwargs):
print("Request finished!")
Notice that the function takes a sender argument, along with wildcard
keyword arguments (**kwargs); all signal receivers must take these
arguments.
We'll look at senders a bit later, but
right now look at the **kwargs argument. All signals send keyword
arguments, and may change those keyword arguments at any time. In the case of
request_finished, it's documented as sending no
arguments, which means we might be tempted to write our signal handling as
my_receiver(sender).
これは間違いです。実際、そうすると Django はエラーを返します。というのも、シグナルに引数が追加される可能性があり、レシーバはその新しい引数を扱えなければならないからです。
レシーバーは同じシグネチャーを持ち、async def を使って宣言された非同期関数になることもできます:
async def my_receiver(sender, **kwargs):
await asyncio.sleep(5)
print("Request finished!")
シグナルは同期でも非同期でも送ることができ、受信側は自動的に正しいコールスタイルに合わせられます。詳細は シグナルを送る を参照してください。
レシーバ関数に接続する¶
受信機を信号に接続する方法は2つあります。手動で接続する方法です:
from django.core.signals import request_finished
request_finished.connect(my_receiver)
あるいは、 receiver() デコレータを使うこともできます:
- receiver(signal, **kwargs)[ソース]¶
- パラメータ:
signal -- 関数を接続するシグナルまたはシグナルのリスト。
kwargs -- 関数 に渡すワイルドカードキーワード引数です。
下記がデコレーターとの繋げ方です:
from django.core.signals import request_finished
from django.dispatch import receiver
@receiver(request_finished)
def my_receiver(sender, **kwargs):
print("Request finished!")
Now, our my_receiver function will be called each time a request finishes.
コードはどこに置くの?
厳密には、シグナル処理と登録のコードは好きな場所に置くことができますが、コードのインポートによる副作用を最小限にするために、アプリケーションのルートモジュールと models モジュールの置くのは避けることを推奨します。
In practice, signal receivers are usually defined in a signals
submodule of the application they relate to. Signal receivers are
connected in the ready() method of your
application configuration class. If
you're using the receiver() decorator, import the signals
submodule inside ready(), this will implicitly
connect signal receivers:
from django.apps import AppConfig
from django.core.signals import request_finished
class MyAppConfig(AppConfig):
...
def ready(self):
# Implicitly connect signal receivers decorated with @receiver.
from . import signals
# Explicitly connect a signal handler.
request_finished.connect(signals.my_receiver)
注釈
The ready() method may be executed more than
once during testing, so you may want to guard your signals from
duplication if your receiver is a bound
method on an instance that may be recreated.
特定の送信者によって送られたシグナルに接続する¶
シグナルの中には何度も送信されるものがありますが、そのようなシグナルの特定のサブセットだけを受信したいと思うかも知れません。例えば、モデルが保存される前に送られるシグナル 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): ...
my_handler 関数は MyModel のインスタンスが保存されたときにだけ呼び出されます。
異なるシグナルは異なるオブジェクトを送信元として使用します。それぞれのシグナルの詳細については 組み込みシグナルのドキュメント を参照する必要があります。
重複したシグナルを防止する¶
When dispatch_uid is not provided, Django identifies each receiver using
its Python object identity and registers it only once. For module-level
functions, static methods, and class methods, the identity is stable, so
connecting the same receiver more than once has no effect:
def my_handler(sender, **kwargs): ...
my_signal.connect(my_handler) # Running this code again is a no-op.
Bound methods, which take a self argument, are different. Their identity
is tied to the specific instance, so connecting the same method from a new
instance registers it as an additional receiver:
def connect_signals():
backend = Backend()
my_signal.connect(backend.my_handler) # A distinct receiver.
connect_signals() # Running this code again registers another receiver.
When using a bound method as a receiver, multiple registrations can be
prevented by supplying a unique dispatch_uid. This identifier will usually
be a string, although any hashable object will suffice. The receiver will only
be bound to the signal once for each unique dispatch_uid value:
from django.core.signals import request_finished
request_finished.connect(my_receiver, dispatch_uid="my_unique_identifier")
シグナルの定義と送信¶
アプリケーションは信号インフラを利用し、独自の信号を提供できます。
カスタムシグナルを使うべきタイミング
シグナルは暗黙の関数呼び出しなので、デバッグが難しくなります。カスタムシグナルの送信側と受信側の両方がプロジェクト内にある場合は、明示的な関数呼び出しを使ったほうがよいでしょう。
シグナルを定義する¶
すべてのシグナルは django.dispatch.Signal インスタンスです。
例:
import django.dispatch
pizza_done = django.dispatch.Signal()
このコードは、pizza_done シグナルを宣言しています。
シグナルを送信する¶
Django でシグナルを同期的に送信する方法は2つあります。
シグナルは非同期に送信することもできます。
- Signal.asend(sender, **kwargs)¶
- Signal.asend_robust(sender, **kwargs)¶
シグナルを送信するには、Signal.send()、Signal.send_robust()、await Signal.asend()、await Signal.asend_robust() のどれかを呼び出します。引数として sender (ほとんどの場合クラス) を指定する必要があり、他のキーワード引数を好きなだけ指定できます。
たとえば、pizza_done シグナルを送信するには、次のようにします:
class PizzaStore:
...
def send_pizza(self, toppings, size):
pizza_done.send(sender=self.__class__, toppings=toppings, size=size)
...
4つのメソッドはすべて、呼び出されたレシーバー関数とそのレスポンス値のリストを表すタプルのペア [(receiver, response), ...] のリストを返します。
send() は send_robust() と異なり、レシーバ関数が発生させた例外をどのようにハンド リングするかという点で異なります。 send() はレシーバが発生させた例外をキャッチしません。そのため、エラーが発生してもすべてのレシーバにシグナルが通知されるとは限りません。
send_robust() は Python の Exception クラスに由来するすべてのエラーをキャッチし、すべてのレシーバにシグナルが通知されるようにします。エラーが発生した場合、エラーを発生させたレシーバのタプルのペアにエラーインスタンスが返されます。
トレースバックは send_robust() を呼び出したときに返されるエラーの __traceback__ 属性に存在します。
asend() は send() と似ていますが、await しなければならないコルーチンです:
async def asend_pizza(self, toppings, size):
await pizza_done.asend(sender=self.__class__, toppings=toppings, size=size)
...
Whether synchronous or asynchronous, receivers will be correctly adapted to
whether send() or asend() is used. Synchronous receivers will be
called using sync_to_async() when invoked via asend(). Asynchronous
receivers will be called using async_to_sync() when invoked via
send(). Similar to the case for middleware,
there is a small performance cost to adapting receivers in this way. Note that
in order to reduce the number of sync/async calling-style switches within a
send() or asend() call, the receivers are grouped by whether or not
they are async before being called. This means that an asynchronous receiver
registered before a synchronous receiver may be executed after the synchronous
receiver. In addition, async receivers are executed concurrently using
asyncio.TaskGroup.
非同期のリクエスト/レスポンス・サイクル以外のすべての組み込みシグナルは Signal.send() を使ってディスパッチされます。
In older versions, async receivers were executed via asyncio.gather().
シグナルを切断する¶
シグナルからレシーバーを切断するには Signal.disconnect() を呼び出します。引数は Signal.connect() の説明と同じです。このメソッドは、レシーバが切断された場合は True を、切断されなかった場合は False を返します。sender が <app label>.<model> への遅延参照として渡された場合、このメソッドは常に None を返します。
引数 receiver は、切断する登録済みのレシーバを指定します。レシーバを識別するために dispatch_uid を使用する場合は None を指定します。