使用post-save信号调用函数时触发Recursion error问题排查
解决自关联Category模型的递归深度错误问题
我来帮你排查这个maximum recursion depth error的问题,核心有两个关键错误导致了这个情况,咱们一步步拆解:
1. post_save信号触发了无限递归循环
你在save_path信号处理函数里调用了instance.save(),这会再次触发post_save信号,而新的信号处理又会再次调用save()——这个循环会一直重复,直到达到Python的递归深度上限,直接抛出错误。
2. 路径构建函数的逻辑错误
你的_recurse_for_parent函数没有正确把父节点的ID添加到路径里,而且还犯了一个变量名错误:模型里的字段是path,但你给instance.children赋值了,这完全不匹配。
修复方案:用模型save方法替代信号(更可靠)
信号虽然方便,但在这种需要修改模型字段并保存的场景下,很容易触发递归。更稳妥的方式是重写模型的save方法来维护path字段:
from django.db import models from your_app.models import SEO, MetaData # 替换成你的实际导入路径 class Category(SEO, MetaData): parent = models.ForeignKey('self', blank=True, null=True, verbose_name='parent category', on_delete=models.CASCADE) path = models.ArrayField(models.IntegerField(), blank=True, default=list) # 设default为空列表,避免null值 def save(self, *args, **kwargs): # 构建当前分类的路径 if self.parent: # 复制父节点的路径,避免修改父节点的原数据 parent_path = self.parent.path.copy() if self.parent.path else [] # 把父节点的ID追加到路径末尾 parent_path.append(self.parent.id) self.path = parent_path else: # 没有父节点(根分类),路径设为空列表 self.path = [] # 调用父类的save方法完成保存 super().save(*args, **kwargs)
如果坚持用信号的修复方法
如果你一定要用post_save信号,需要避免无限递归,并且修正路径逻辑:
def _recurse_for_parent(cat_obj, path=None): if not path: path = [] if cat_obj.parent is not None: # 先递归获取父节点的路径 path = _recurse_for_parent(cat_obj.parent, path) # 将父节点ID加入路径 path.append(cat_obj.parent.id) return path def save_path(sender, instance, **kwargs): # 先计算预期的路径 expected_path = [] if instance.parent_id: expected_path = _recurse_for_parent(instance.parent) expected_path.append(instance.parent.id) # 只有当当前path和预期不符时才更新,避免无限递归 if instance.path != expected_path: instance.path = expected_path # 使用update_fields只更新path字段,减少不必要的操作 instance.save(update_fields=['path']) post_save.connect(save_path, sender=Category)
这个方法里,我们通过update_fields限定只更新path字段,并且增加了路径对比,只有当路径需要修改时才执行保存,最大程度避免递归触发。
内容的提问来源于stack exchange,提问作者user3541631
相关产品推荐
相关产品推荐

