信号 — Django 6.0.4 documentation
创始人
2026-06-13 10:59:18

信号

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

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

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

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

Warning

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

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

监听信号

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

Signal.connect(receiver, sender=None, weak=True, dispatch_uid=None)[source]
参数:
  • 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!")

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

连接接收器函数

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

from django.core.signals import request_finished

request_finished.connect(my_callback)

或者,你可以使用一个 receiver() 装饰器:

receiver(signal, **kwargs)[source]
参数:
  • 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 子模块中定义。信号接收者在应用程序的 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)

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_callback, dispatch_uid="my_unique_identifier")

定义和发送信号

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

何时使用自定义信号

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

定义信号

class Signal[source]

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

例如:

import django.dispatch

pizza_done = django.dispatch.Signal()

这声明了一个 pizza_done 信号。

发送信号

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

Signal.send(sender, **kwargs)[source]
Signal.send_robust(sender, **kwargs)[source]

信号也可以异步发送。

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

除了在异步请求-响应周期中的信号外,所有内置信号都使用 Signal.send() 进行分发。

断开信号

Signal.disconnect(receiver=None, sender=None, dispatch_uid=None)[source]

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

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

⚠️
本网站信息内容及素材来源于网络采集或用户发布,如涉及侵权,请及时联系我们,发送链接至2697952338@QQ.COM,我们将第一时间进行核实与删除处理。

相关内容

热门资讯

智易购APP无资质经营贵金属期...   智易购APP如今受害者众多,投资人在该平台做交易,只要是大资金买入,基本都是反向走势,一买涨,就...
斑马投资APP虚假商品订购交易...   网上宣传的斑马投资APP交易平台真实靠谱吗?在这个平台做交易出现严重亏损,平台莫名其妙的卡顿和跟...
吉鑫乐享APP夸大宣传诱导充值...   网上众多的白银交易软件,大部分背后是长沙白安安网络科技有限公司,看似正规的平台,其实就是在给投资...
易掘金APP无资质冒充商城订购...   易掘金APP大肆鼓吹其商品订购业务“收益可观、操作灵活”,吸引大量普通民众投入资金,不少人甚至将...
充钱就能赚?大吉订购APP&q...   大吉订购APP是骗局吗?投资者在大吉订购软件上做贵金属交易亏钱了,这是正常的亏损?大吉订购这个交...
铂银壹号APP贵金属投资骗局,...   中小投资者在网上参与商品现货订购交易,表面看似合规合法,实则暗藏难以察觉的风险。商品现货交易本应...
兴鑫淘金APP无牌照运营投资骗...   兴鑫淘金APP投资人亏损的资金能追回?该平台是否有期货经营资质?为什么一个现货订购平台能做高杠杆...
警惕!铂银掘金APP被指收割投...   投资者警惕!“铂银掘金APP”被指收割投资者,高额手续费与后台操控成亏损元凶  近期,甘肃西北商...
八方淘金APP商品订购涉嫌非法...   八方淘金APP(甘肃西北商品交易市场有限公司)投资亏损如何挽回?该平台是否具备合法资质?众多投资...
5元起投实现财富自由?指尖订购...   贵州云上大宗商品交易有限公司(贵州云上大宗现货商品交易中心有限公司)并非期货交易平台,旗下金鳞订...