Django CBV 源码解读:一个请求是怎么找到你的 get() 方法的

Django CBV 源码解读:一个请求是怎么找到你的 get() 方法的

作者:呱呱复呱呱 | DRF 系列第一篇


前言

刚从前端转过来学 Django,第一次看到这种写法时懵了:

python 复制代码
class TestView(APIView):
    def get(self, request):
        return Response({"message": "hello"})

URL 里这样注册:

python 复制代码
from django.urls import path
from . import views

urlpatterns = [
    path("test/", views.TestView.as_view()),
]

问题来了:

  • as_view() 是什么?为什么不直接写 TestView
  • 请求进来怎么就自动找到 get() 方法了?
  • 我要是定义了 post(),POST 请求又怎么知道去调它?

带着这些问题去翻了源码,一下子全想通了。


FBV vs CBV

先说背景。Django 写视图有两种方式:

FBV(函数视图)

python 复制代码
def test_view(request):
    if request.method == 'GET':
        return JsonResponse({"message": "hello"})
    if request.method == 'POST':
        name = request.POST.get('name')
        return JsonResponse({"message": f"hello {name}"})

CBV(类视图)

python 复制代码
class TestView(APIView):
    def get(self, request):
        return Response({"message": "hello"})

    def post(self, request):
        name = request.data.get('name')
        return Response({"message": f"hello {name}"})

CBV 的好处很明显:

  • 不用写一堆 if request.method ==,每种请求方法对应一个函数,职责清晰
  • 可以继承复用,APIView 帮你处理好了认证、权限、解析器等通用逻辑
  • 代码结构更接近面向对象

第一个问题:as_view() 是什么

URL 注册时写的是:

python 复制代码
path('test/', TestView.as_view()),

而不是:

python 复制代码
path('test/', TestView),

为什么?

Django 的 URL 路由系统期望拿到一个函数 ,调用它处理请求。但 TestView 是一个,不能直接当函数用。

as_view() 就是负责把类转成函数的。看源码:

python 复制代码
# django/views/generic/base.py

@classonlymethod
def as_view(cls, **initkwargs):
    def view(*args, **kwargs):
        self = cls(**initkwargs)   # 每次请求来了,实例化一个新对象
        return self.dispatch(*args, **kwargs)  # 转交给 dispatch 处理
    return view                    # 返回这个函数

简化理解:

scss 复制代码
as_view() 返回一个函数 view
URL 匹配时调用 view(request)
view 内部 → 实例化类 → 调用 dispatch()

每次请求都会创建一个新的类实例,所以不用担心多个请求之间状态互相干扰。


第二个问题:dispatch() 怎么找到 get() 方法

这是整个 CBV 最核心的逻辑,源码在这里:

python 复制代码
# django/views/generic/base.py

def dispatch(self, request, *args, **kwargs):
    if request.method.lower() in self.http_method_names:
        handler = getattr(
            self, request.method.lower(), self.http_method_not_allowed
        )
    else:
        handler = self.http_method_not_allowed
    return handler(request, *args, **kwargs)

逐行拆解:

第一步:把请求方法转小写

python 复制代码
request.method          # 'GET'
request.method.lower()  # 'get'

第二步:用 getattr 动态取方法

python 复制代码
handler = getattr(self, 'get', self.http_method_not_allowed)

这里利用了 Python 的一个特性:方法也是对象的属性

python 复制代码
class TestView(APIView):
    def get(self, request):   # 定义了 get 方法
        ...

view = TestView()
view.get                      # 可以像属性一样取到这个方法
getattr(view, 'get')          # 完全等价

所以 getattr(self, 'get', self.http_method_not_allowed) 的意思是:

去 self 上找名字叫 'get' 的属性(方法),找不到就用 http_method_not_allowed 兜底

第三步:调用找到的方法

python 复制代码
return handler(request, *args, **kwargs)
# 等价于 self.get(request, *args, **kwargs)

完整请求流程

lua 复制代码
客户端发送 GET /api/v1/test/

  ↓ URL 路由匹配

TestView.as_view() 返回的 view 函数被调用

  ↓

实例化 TestView,调用 dispatch(request)

  ↓

request.method.lower() → 'get'
getattr(self, 'get', http_method_not_allowed) → 找到 self.get 方法

  ↓

self.get(request) 被调用

  ↓

return Response({"message": "hello"})

  ↓

客户端收到响应

如果客户端发了一个 DELETE 请求,但你没有定义 delete() 方法:

python 复制代码
getattr(self, 'delete', self.http_method_not_allowed)
→ 找不到 self.delete
→ 返回默认值 self.http_method_not_allowed
→ 自动响应 405 Method Not Allowed

框架帮你兜底了,不需要自己处理。


View vs APIView

上面说的 dispatch 是 Django 原生 View 的逻辑。DRF 的 APIView 继承了它,并在 dispatch 里做了增强:

python 复制代码
# rest_framework/views.py

def dispatch(self, request, *args, **kwargs):
    # 1. 把原生 request 包装成 DRF Request
    request = self.initialize_request(request, *args, **kwargs)
    
    try:
        # 2. 执行认证、权限、限流检查
        self.initial(request, *args, **kwargs)
        
        # 3. 和原生 View 一样,找对应方法执行
        if request.method.lower() in self.http_method_names:
            handler = getattr(self, request.method.lower(),
                              self.http_method_not_allowed)
        else:
            handler = self.http_method_not_allowed
        response = handler(request, *args, **kwargs)

    except Exception as exc:
        # 4. 统一异常处理
        response = self.handle_exception(exc)

    return self.finalize_response(request, response, *args, **kwargs)

和原生 View 相比,APIView 多了四件事:

原生 View APIView
request 对象 Django HttpRequest DRF Request(支持 request.data
认证 手动处理 自动执行
权限 手动处理 自动执行
异常处理 手动处理 统一兜底

这也是为什么写 DRF 接口要继承 APIView 而不是 View:不用自己处理这些通用逻辑,专注写业务就好。


DRF Request 和原生 Request 的区别

APIView 把原生 request 包了一层,最直观的区别:

python 复制代码
# 原生 View
request.POST.get('name')   # 只能拿表单数据
json.loads(request.body)   # JSON 要手动解析

# APIView
request.data.get('name')   # 自动解析 JSON / 表单 / multipart
request.query_params       # 等价于 request.GET,语义更清晰

总结

看完源码,一开始的三个问题都有答案了:

as_view() 是什么? 把类转成函数,让 URL 路由系统能用它。每次请求进来都会实例化一个新对象。

请求怎么找到 get() 方法? dispatch()getattr(self, request.method.lower(), ...) 动态取方法。Python 里方法也是属性,所以 getattr 能取到。

APIViewView 多了什么? 包装了 request 对象,自动执行认证/权限/限流,统一处理异常。


系列持续更新,下一篇:DRF Serializer 核心机制 --- 数据是怎么进来又怎么出去的

相关推荐
二宝哥7 小时前
07.Python运算符详解与编码基础
python
巴啦啦小魔仙变身7 小时前
SIGINT = Signal Interrupte
linux·服务器·python
Maiko Star7 小时前
Python项目实战——网络机器人(爬虫)
爬虫·python·机器人
xlrqx7 小时前
2026年沧州家电清洗培训成本核算方法及成本控制要点
大数据·python
许彰午7 小时前
85_Python Flask开发RESTful API
python·flask·restful
梦想不只是梦与想7 小时前
OmegaConf 配置管理库的使用
python·omegaconf·结构化配置
@陈小鱼8 小时前
MATLAB+Python:基于小样本卷积神经网络的 PPG 血压预测
人工智能·python·机器学习·matlab·脉搏波·血压·一维卷积神经网络
卷无止境8 小时前
Python数据进阶专题:用声明式验证让数据管道告别"惊喜"
后端·python·数据分析
码云骑士9 小时前
61-LangChain-vs-LlamaIndex-选型对比-功能矩阵-混用实践
python·线性代数·矩阵·langchain
阿豪只会阿巴9 小时前
两小时快速入门 FastAPI--第一回
开发语言·python·fastapi