Django Signals 会触发循环引用,因为 post_save 或 pre_save 信号处理函数中若调用 save(),将再次触发同信号,形成无限递归;Django 不做递归防护,易致栈溢出或死锁。

为什么 Django Signals 会触发循环引用
当 post_save 或 pre_save 信号监听的模型在信号处理函数里又调用了 save(),而该保存又再次触发同个信号时,就形成循环。常见于:更新关联字段、同步计数器、自动生成 slug 等场景。Django 不做信号递归防护,一旦没控制好,可能栈溢出或数据库死锁。
用 dispatch_uid 和 sender 精确绑定信号
不加 dispatch_uid 的信号注册,在热重载或多次导入时可能重复注册,导致一次 save 触发多次 handler;而模糊的 sender=Model 可能误匹配子类。必须显式指定唯一 ID 和精确 sender:
@receiver(post_save, sender=User, dispatch_uid="update_profile_on_user_save")
def update_profile_on_user_save(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
-
dispatch_uid必须是字符串常量,不能是变量或函数返回值 - 避免用
sender=models.Model这种宽泛写法 - 如果 handler 在 apps.py 中注册,优先用
ready()方法而非模块顶层
在 signal handler 中禁用信号再 save
最直接的破环方式:在需要内部调用 save() 前,临时断开当前信号。常用两种做法:
- 用
django.dispatch.Signal.disconnect()手动解绑(需保留原 receiver 引用) - 更推荐:在
save()时传入自定义 flag,让 signal handler 主动跳过
例如:
立即学习“Python免费学习笔记(深入)”;
# model.py
class Article(models.Model):
title = models.CharField(max_length=200)
slug = models.SlugField(blank=True)
<pre class="brush:php;toolbar:false;">def save(self, *args, **kwargs):
# 关键:加个标记,告诉 signal 不要响应这次 save
skip_signal = kwargs.pop('skip_signal', False)
super().save(*args, **kwargs)
if not skip_signal:
self.generate_slug()
def generate_slug(self):
self.slug = slugify(self.title)
self.save(skip_signal=True) # ← 这次 save 不触发 post_save
对应 signal handler 加上守卫:
@receiver(post_save, sender=Article)
def ensure_slug_exists(sender, instance, created, **kwargs):
if hasattr(instance, '_signal_handled') or kwargs.get('raw'):
return
if not instance.slug:
instance.generate_slug()
用 transaction.on_commit() 推迟执行
当逻辑必须依赖刚保存的数据、又怕信号嵌套时,把副作用推迟到事务提交后执行——既避开当前 save 的信号链,又能确保数据已落库:
@receiver(post_save, sender=Order)
def send_order_notification(sender, instance, created, **kwargs):
if not created:
return
from django.db import transaction
transaction.on_commit(lambda: notify_via_email(instance.id))
- 适用于发邮件、调外部 API、写日志等非事务强一致性操作
- 注意:lambda 里不要捕获 mutable 对象(如 instance 字段),应传 ID 后查库
- 若不用事务(比如
ATOMIC_REQUESTS=False),on_commit不生效
循环引用真正难缠的地方不在语法,而在业务逻辑是否天然耦合——比如 A 更新触发 B 更新,B 更新又反向改 A。这时候该重构模型关系或引入事件总线,而不是靠更多 signal 技巧兜底。


















