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 核心机制 --- 数据是怎么进来又怎么出去的

相关推荐
曲幽6 小时前
刚部署的 LibreTranslate 频频翻车?我掏出了 20 年前的 StarDict 词典,用 FastAPI 搭了个本地词典翻译 API
python·fastapi·web·translate·goldendict·libretranslate·stardict·pystardict
荣码6 小时前
用Streamlit给AI应用套个界面,10行代码出Web页面
java·python
兵慌码乱16 小时前
基于Python+PyQt5+SQLite的药房管理系统实现:事务一致性与界面解耦全流程解析
python·sqlite·信号与槽·pyqt5·数据库设计·桌面应用开发·事务处理
金銀銅鐵17 小时前
[Python] 体验用欧几里得算法计算最大公约数的过程
python·数学
FreakStudio21 小时前
W55MH32L-EVB 上手测评:硬件 TCP/IP 加持的以太网单片机,MicroPython 零门槛开发
python·单片机·嵌入式·大学生·面向对象·并行计算·电子diy·电子计算机
用户0332126663671 天前
使用 Python 从零创建 Word 文档
python
Csvn1 天前
Python 两大经典坑点 —— 可变默认参数 & 闭包延迟绑定
后端·python
曲幽1 天前
别再用网页翻译看源码了!你的私人翻译神器LibreTranslate,部署避坑指南来了
python·docker·web·pot·translate·libretranslate·arogstranslate
用户556918817531 天前
#从脚本到独立程序:Python + Playwright 批量抓取的完整踩坑记录
python·自动化运维