# Django 6.0.7 documentation

# 信号

Django有一个“信号调度器(signal dispatcher)”，用来帮助解耦的应用获知框架内任何其他地方发生了操作。简单地说，信号允许某些 发送器 去通知一组 接收器 某些操作发生了。当许多代码段都可能对同一事件感兴趣时，信号特别有用。

例如，第三方应用程序可以注册以接收设置更改的通知：

    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 的 内置信号 允许用户代码在某些操作发生时收到通知。

你也可以定义并发送自己的自定义信号。请参阅下面的 定义和发送信号。

Warning

信号看起来是松散耦合的表现，但它们很快会导致难以理解、调整和调试的代码。

在可能的情况下，你应该选择直接调用处理代码，而不是通过信号进行分发。

## 监听信号

要接收信号，使用 `Signal.connect()` 方法注册一个 接收器 函数。当发送信号时调用接收器。信号的所有接收器函数都按照注册时的顺序一个接一个调用。

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=False` when you call the signal's `connect()` 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!")

信号可以同步或异步发送，接收器将自动适应正确的调用方式。更多信息请参阅 发送信号。

### 连接接收器函数

有两种方法可以将接收器连接到信号。你可以选择手动连接线路：

    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)

Note

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

## 定义和发送信号

您的应用程序可以利用信号基础设施并提供自己的信号。

何时使用自定义信号

信号是隐式函数调用，这使得调试更加困难。如果你的自定义信号的发送器和接收器都在你的项目内，最好使用显式函数调用。

### 定义信号

class Signal

所有的信号都是 `django.dispatch.Signal` 的实例。

例如：

    import django.dispatch

    pizza_done = django.dispatch.Signal()

这声明了一个 `pizza_done` 信号。

### 发送信号

在 Django 中有两种方式可以同步发送信号。

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

这四种方法都返回一个元组对的列表 `[(receiver, response), ...]`，表示被调用的接收器函数列表和它们的响应值。

`send()` 和 `send_robust()` 在处理接收器函数所引发异常的方式上有所不同。 `send()` 不 捕获接收器引起的任何异常；它只是允许错误传播。因此，并非所有的接收器都会在出现错误时被通知信号。

`send_robust()` 捕获从 Python的 `Exception` 类派生的所有错误，并确保所有接收器都收到信号通知。如果发生错误，将在引发错误的接收器的元组对中返回错误实例。

回溯出现在调用 `send_robust()` 时返回的错误中的 `__traceback__` 属性中。

''asend（）'' 类似于 ''send（）''，但它是一个必须等待的协程：

    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()` 进行分发。

Changed in Django 6.1:

In older versions, async receivers were executed via `asyncio.gather()`.

## 断开信号

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

要从信号中断开接收者，调用 `Signal.disconnect()`。参数与 `Signal.connect()` 中描述的一样。如果成功断开接收者，该方法返回 `True`，否则返回 `False`。当将 `sender` 作为对 `<app label>.<model>` 的延迟引用传递时，该方法始终返回 `None`。

`receiver` 参数表明要断开的接收器。它可以是 `None` 如果 `dispatch_uid` 已经被用来标识接收器。