bigywater.tech
Django · 103

Django Signal 的发送与接收

Signal 是 Django 内置的观察者模式。这篇不涉及模型,只用一个自建的 Signal 把它的规则逐条跑一遍:调用顺序、返回值、sender 过滤、异常、去重与开销。

连接顺序
receiver 按 connect 的先后依次被调用
send / send_robust
send 遇异常抛出并中断,send_robust 捕获并继续
**kwargs
receiver 签名必须带,否则 connect 时报错

发送与接收

实测 · 本机执行输出
调用顺序: ['r1', 'r2(x=42)']
send() 返回: [('r1', 'from r1'), ('r2', 'from r2')]
connect 一个没有 **kwargs 的函数 → ValueError: Signal receivers must accept keyword arguments (**kwargs).

send() 是同步的:在调用方的线程里一个个执行 receiver,全部执行完才返回。

sender 过滤

实测 · 本机执行输出
sender=A: ['only_a', 'any']
sender=B: ['any']

receiver 出错时

实测 · 本机执行输出
send() 抛出: receiver failed  已执行: ['ok1', 'boom']
send_robust() 已执行: ['ok1', 'boom', 'ok2']
  返回: [('ok1', '1'), ('boom', "ValueError('receiver failed')"), ('ok2', '2')]

Django 自带的模型信号(post_save 等)用的是 send(),所以某个 receiver 抛异常会打断后面的 receiver,并让调用方(比如 save())也失败。自己发的信号如果不希望被 receiver 拖垮,用 send_robust(),但要自己检查返回值里有没有异常。

去重与断开

实测 · 本机执行输出
同一个函数 connect 两次,send 一次调用 1 次
用了 dispatch_uid: x, x, y → 调用 2 次(x 只算一个)
disconnect(dispatch_uid="x") 返回 True
之后调用 1 次
@receiver 装饰器连接后调用: ['f', 'deco']

开销

实测 · 本机执行输出
   0 个 receiver:每次 send   0.12 µs
   1 个 receiver:每次 send   0.78 µs
   5 个 receiver:每次 send   1.87 µs
  20 个 receiver:每次 send   5.61 µs

信号机制本身是微秒级的,不需要为它「省」;要注意的是 receiver 里执行的代码。

分步演示

Signal 的规则
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / Python 3.14.7 / Django 6.1.1(默认用 SQLite,其它数据库会在文中注明),用 django.test.Client 或真实的本机服务器进程实测;计时用 time.perf_counter,数字每次运行会有小幅抖动。使用自建的 django.dispatch.Signal,不涉及数据库
    • 没有验证:多线程同时 send、receiver 用 weak 引用时被回收(第 109 篇)、async receiver(Django 5.0 起 send 可以调用 async receiver,本文没有测)、use_caching 参数的效果
    京ICP备2026067502号-1京公网安备11010502063487号