# Django 5.2.9 ドキュメント

# シグナル

Django には "シグナルディスパッチャ" があり、フレームワークの他の場所でアクションが発生したときに、ほかのアプリケーションが通知を受けるのを助けてくれます。簡単に言うと、シグナルは特定の 送り手 が、あるアクションが発生したことを一連の 受け手 に通知できるようにします。特に、多くのコードが同じイベントに関連している場合に便利です。

例えば、サードパーティのアプリを登録して、設定変更の通知を受けることができます:

    from django.apps import AppConfig
    from django.core.signals import setting_changed

    def my_callback(sender, **kwargs):
        print("Setting changed!")

    class MyAppConfig(AppConfig):
        ...

        def ready(self):
            setting_changed.connect(my_callback)

Django の 組み込みのシグナル は、ユーザコードに特定のアクションを通知します。

また、独自のカスタムシグナルを定義して送信することもできます。以下の シグナルの定義と送信 を参照してください。

警告

シグナルは疎結合のように見えますが、すぐに理解や調整、デバッグが難しいコードにつながります。

可能であれば、シグナルでディスパッチするのではなく、処理コードを直接呼び出すことを選ぶべきです。

## シグナルを待ち受ける

シグナルを受信するには、 `Signal.connect()` メソッドを使って receiver 関数を登録します。シグナルが送信されると、レシーバ関数が呼び出されます。シグナルのすべてのレシーバ関数は、登録された順番に1つずつ呼び出されます。

Signal.connect(receiver, sender=None, weak=True, dispatch_uid=None)
  パラメータ:

    - receiver -- このシグナルに接続されるコールバック関数です。詳しくは レシーバ関数 を参照してください。

    - sender -- シグナルを受信する送信者を指定します。詳しくは 特定の送信者によって送られたシグナルに接続する を参照してください。

    - weak -- Django はデフォルトでシグナルハンドラを弱い参照として保存します。従って、レシーバがローカル関数の場合、ガベージコレクションされる可能性 があります。これを防ぐには、シグナルの `connect()` メソッドを呼び出すときに `weak=False` を渡してください。

    - dispatch_uid -- シグナルが重複して送信される可能性がある場合の、シグナル受信機の一意な識別子。詳しくは 重複したシグナルを防止する を参照してください。

HTTP リクエストが終了するたびに呼び出されるシグナルを登録することで、この仕組みを見てみましょう。ここでは `request_finished` シグナルに接続します。

### レシーバ関数

まず、レシーバ関数を定義する必要があります。レシーバはPythonの関数やメソッドであれば何でもかまいません:

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

この関数は `sender` 引数とワイルドカードキーワード引数 (`**kwargs`) を取ることに注意してください。すべてのシグナルハンドラはこれらの引数を取らなければなりません。

送信者については もう少し後で 見るので、今は `**kwargs` 引数を見てください。すべてのシグナルはキーワード引数を送信し、いつでもそのキーワード引数を変更できます。 `request_finished` の場合、引数を送らないようにドキュメント化されているので、シグナル処理を `my_callback(sender)` と書きたくなるかもしれません。

これは間違いです。実際、そうすると Django はエラーを返します。というのも、シグナルに引数が追加される可能性があり、レシーバはその新しい引数を扱えなければならないからです。

レシーバーは同じシグネチャーを持ち、`async def` を使って宣言された非同期関数になることもできます:

    async def my_callback(sender, **kwargs):
        await asyncio.sleep(5)
        print("Request finished!")

シグナルは同期でも非同期でも送ることができ、受信側は自動的に正しいコールスタイルに合わせられます。詳細は シグナルを送る を参照してください。

### レシーバ関数に接続する

受信機を信号に接続する方法は2つあります。手動で接続する方法です:

    from django.core.signals import request_finished

    request_finished.connect(my_callback)

あるいは、 `receiver()` デコレータを使うこともできます:

receiver(signal, **kwargs)
  パラメータ:

    - signal -- 関数を接続するシグナルまたはシグナルのリスト。

    - kwargs -- 関数 に渡すワイルドカードキーワード引数です。

下記がデコレーターとの繋げ方です:

    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` サブモジュールで定義されます。シグナルレシーバーはアプリケーションの configuration クラス の `ready()` メソッドで接続されます。 `receiver()` デコレータを使っている場合は、 `ready()` の中にある `signals` サブモジュールをインポートしてください、これによりシグナルハンドラが暗黙的に接続されます:

    from django.apps import AppConfig
    from django.core.signals import request_finished

    class MyAppConfig(AppConfig):
        ...

        def ready(self):
            # Implicitly connect signal handlers decorated with @receiver.
            from . import signals

            # Explicitly connect a signal handler.
            request_finished.connect(signals.my_callback)

注釈

テスト中に `ready()` メソッドが複数回実行されることがあるため、特にテスト内でシグナルを送信する予定がある場合は、シグナルの重複を防ぐ ためにシグナルを保護することを検討するとよいでしょう。

### 特定の送信者によって送られたシグナルに接続する

シグナルの中には何度も送信されるものがありますが、そのようなシグナルの特定のサブセットだけを受信したいと思うかも知れません。例えば、モデルが保存される前に送られるシグナル `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` のインスタンスが保存されたときにだけ呼び出されます。

異なるシグナルは異なるオブジェクトを送信元として使用します。それぞれのシグナルの詳細については 組み込みシグナルのドキュメント を参照する必要があります。

### 重複したシグナルを防止する

状況によっては、レシーバーをシグナルに接続するコードが複数回実行されることがあります。そのため、レシーバ関数が複数回登録され、シグナルイベントに対して複数回呼び出される可能性があります。例えば、 `ready()` メソッドがテスト中に複数回実行されるかもしれません。より一般的には、シグナル登録はインポートされた回数だけ実行されるため、 シグナルを定義したモジュールをインポートしたプロジェクトではどこでも発生します。

この動作が問題となる場合 (シグナルを使用してモデルが保存されるたびにメールを送信する場合など)、受信関数を識別するために `dispatch_uid` 引数に一意な識別子を渡します。この識別子は通常文字列ですが、ハッシュ可能なオブジェクトであれば何でもかまいません。この結果、受信関数は一意な `dispatch_uid` の値ごとに一度だけシグナルにバインドされることになります:

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

同期・非同期いずれのレシーバーも、呼び出し時に `send()` か `asend()` のどちらを使うかに応じて自動的に適応されます。 `asend()` 経由で呼び出された場合、同期レシーバーは `sync_to_async()` を介して実行されます。逆に `send()` 経由で呼び出された場合、非同期レシーバーは `async_to_sync()` を介して実行されます。 ミドルウェアの場合 と同様、この適応にはわずかなパフォーマンスコストが発生します。 `send()` または `asend()` の内部で同期・非同期の呼び出しスタイルを切り替える回数を減らすため、実行前にレシーバーは「同期か非同期か」でグループ分けされます。その結果、同期レシーバーより前に登録された非同期レシーバーが後に実行されることがあります。さらに、非同期レシーバーは `asyncio.gather()` を用いて並行実行されます。

非同期のリクエスト/レスポンス・サイクル以外のすべての組み込みシグナルは `Signal.send()` を使ってディスパッチされます。

## シグナルを切断する

Signal.disconnect(receiver=None, sender=None, dispatch_uid=None)

シグナルからレシーバーを切断するには `Signal.disconnect()` を呼び出します。引数は `Signal.connect()` の説明と同じです。このメソッドは、レシーバが切断された場合は `True` を、切断されなかった場合は `False` を返します。`sender` が `<app label>.<model>` への遅延参照として渡された場合、このメソッドは常に `None` を返します。

引数 `receiver` は、切断する登録済みのレシーバを指定します。レシーバを識別するために `dispatch_uid` を使用する場合は `None` を指定します。