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 能取到。
APIView 比 View 多了什么? 包装了 request 对象,自动执行认证/权限/限流,统一处理异常。
系列持续更新,下一篇:DRF Serializer 核心机制 --- 数据是怎么进来又怎么出去的