You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

手动用extend_schema装饰函数引发RecursionError问题排查

问题原因分析

1. 动态装饰引发递归错误的核心原因

你在__init__方法中每次实例化视图集时,都会用extend_schema重新装饰一次list_history方法。但extend_schema的本质是给原方法套一层包装函数,每次装饰都会生成一个新的包装层。当drf-spectacular多次生成schema(比如缓存失效、多次请求schema接口)时,视图集被反复实例化,list_history方法会被层层嵌套装饰。随着嵌套层数不断累积,最终超过Python的递归深度限制,触发RecursionError。

drf-spectacular在处理schema时,会调用方法的装饰器链来解析接口元数据,多层嵌套的装饰器会导致get_operation和is_excluded等方法在遍历装饰器链时陷入无限递归。

2. 报错偶发的原因

drf-spectacular默认会对生成的schema进行缓存,前几次刷新swagger页面时,直接读取缓存的schema结果,不会重新触发视图集实例化和装饰逻辑。当缓存过期、或者服务器进程中多次处理schema生成请求时,视图集被多次实例化,list_history的装饰层数逐渐累积,直到超过递归深度阈值才会报错,因此表现为偶发。

修复方案

不要在__init__中动态装饰方法,改用类级别的extend_schema_view来实现延迟绑定序列化器,这样只会在类定义时处理一次装饰逻辑,不会重复嵌套:

from drf_spectacular.utils import extend_schema, extend_schema_view
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.response import Response

class HistoricalModelViewSet(viewsets.ModelViewSet):
    # 子类必须定义这个属性
    historical_serializer_class = None

    @action(detail=False, methods=["get"], url_path="history", url_name="list-history-list")
    def list_history(self, request):
        # 补充你的history_items和context获取逻辑
        history_items = self.get_queryset().history.all()
        context = self.get_serializer_context()
        serializer = self.historical_serializer_class(history_items, many=True, context=context)
        return Response(serializer.data)

    # 用extend_schema_view实现子类序列化器的动态绑定
    schema = extend_schema_view(
        list_history=extend_schema(
            responses=lambda self: {200: self.historical_serializer_class(many=True)}
        )
    )

这样修改后,extend_schema的responses参数通过lambda延迟求值,只有在生成schema时才会访问实例的historical_serializer_class,同时只会对list_history方法装饰一次,彻底避免重复嵌套导致的递归错误。

另外,也可以在子类中显式给list_history添加extend_schema装饰,这种方式更直观,但需要每个子类都重复写装饰代码:

class StuffViewSet(HistoricalModelViewSet):
    historical_serializer_class = StuffHistoricalSerializer

    @extend_schema(responses={200: StuffHistoricalSerializer(many=True)})
    def list_history(self, request):
        return super().list_history(request)

内容的提问来源于stack exchange,提问作者himmip

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 10:22:31