DRF序列化器遇自引用或相互引用模型时会抛RecursionError,因默认递归展开关系字段无层级控制;应使用RecursiveField处理树形结构、避免to_representation中手动访问反向关系、禁用depth、模型层用字符串引用外键、序列化器中用字符串名称替代导入、@cached_property缓存属性、预取关系时限制层数。
DRF 序列化器遇到自引用或相互引用模型时,serializer.data 会直接抛出 RecursionError: maximum recursion depth exceeded ——这不是配置问题,是默认行为。根本原因在于序列化器递归展开关系字段(如 ForeignKey、ManyToManyField 或自定义方法返回关联对象)时,没做层级控制或引用去重。
RecursiveField 处理自引用模型(如树形结构)典型场景:部门表 Department 通过 parent = models.ForeignKey('self', ...) 实现父子嵌套。直接在 DepartmentSerializer 中用 children = DepartmentSerializer(many=True) 会导致无限递归。
正确做法是引入第三方字段:
pip install djangorestframework-recursive
RecursiveField() 替代嵌套序列化器RecursiveField() 内部靠栈追踪已序列化对象 ID,自动跳过重复引用示例:
from rest_framework_recursive.fields import RecursiveFieldfrom rest_framework import serializersclass DepartmentSerializer(serializers.ModelSerializer): children = serializers.ListField( source='get_children', child=RecursiveField() ) class Meta: model = Department fields = ['id', 'name', 'parent', 'children']
注意:source='get_children' 必须是返回 QuerySet 的方法,且不能在方法体内再次调用本序列化器。
to_representation 中手动触发反向关系常见错误:为“补全数据”在 to_representation 里主动访问 obj.related_set.all(),而该 related set 又反向指向当前模型(如评论 Comment 关联 Post,Post 又有 comment_set),形成隐式循环。
规避方式:
SerializerMethodField + 显式控制返回字段(不带嵌套序列化器)PrimaryKeyRelatedField 或 SlugRelatedField,只传 ID/标识符depth 参数:设 depth = 0 或干脆不写,避免 DRF 自动展开多层关系循环引用常发生在两个模型互相 ForeignKey 引用,且各自 serializers.py 又相互导入对方序列化器时。此时报错可能表现为 ImportError 或运行时报 NameError。
关键修复点在模型定义本身:
user = models.ForeignKey('User', ...) 而非 user = models.ForeignKey(User, ...)
from xxx import Yyy 移到模型文件末尾(而非顶部),确保类定义先完成再导入from .serializers import OtherSerializer,改用字符串名称:other = serializers.PrimaryKeyRelatedField(queryset=OtherModel.objects.all())
这样 Python 解释器不会在加载阶段尝试解析未定义的类,把校验推迟到实际访问字段时。
@cached_property 缓存计算字段,防止重复序列化触发多次关系查询当序列化器字段依赖 @property 方法,而该方法内部又访问了可能引发循环链路的关联对象(例如 def get_full_path(self): return self.parent.get_full_path() + [self.name]),每次访问都会重新走一遍路径,极易超深度或重复查库。
稳妥做法:
@cached_property(Django 2.0+),首次计算后缓存结果to_representation 开头统一预取必要关系:prefetch_related('parent__parent__parent'),但需严格限制层数WITH RECURSIVE),不在 Python 层递归最易被忽略的是:循环引用未必出现在字段定义里,而藏在 to_representation 的任意一行代码中——只要它间接触发了另一个序列化器实例的创建,就可能破防。