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() 方法注册一个 接收器 函数。当发送信号时调用接收器。信号的所有接收器函数都按照注册时的顺序一个接一个调用。
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() 装饰器:
以下是你如何使用装饰器连接:
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")
您的应用程序可以利用信号基础设施并提供自己的信号。
何时使用自定义信号
信号是隐式函数调用,这使得调试更加困难。如果你的自定义信号的发送器和接收器都在你的项目内,最好使用显式函数调用。
所有的信号都是 django.dispatch.Signal 的实例。
例如:
import django.dispatch
pizza_done = django.dispatch.Signal()
这声明了一个 pizza_done 信号。
在 Django 中有两种方式可以同步发送信号。
信号也可以异步发送。
要发送信号,请调用 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() 进行分发。
In older versions, async receivers were executed via asyncio.gather().
要从信号中断开接收者,调用 Signal.disconnect()。参数与 Signal.connect() 中描述的一样。如果成功断开接收者,该方法返回 True,否则返回 False。当将 sender 作为对 <app label>.<model> 的延迟引用传递时,该方法始终返回 None。
receiver 参数表明要断开的接收器。它可以是 None 如果 dispatch_uid 已经被用来标识接收器。
8月 05, 2026