Django + Vue 学习日志(05CBV)
PART05:CBV 源码拆解、as_view() 机制与请求调度链
记录时间:2026年8月6日
环境:Fedora 44 + Python 3.12.13 + Django 5.2.16
今日前言
今天进入 Django 最"神秘"的部分 —— Class-Based Views 的源码。
从 as_view() 到 dispatch() 到 get() / post(),每一步都追到底。
核心目标:看到 RegisterView.as_view() 时,脑子里能跑完整个调用链。
一、as_view() 为什么必须是 @classmethod?
问题起源
"这里一定要有
@classmethod么?"
正确答案
必须有。 原因有三:
① Python 语法要求
@classmethod 的唯一作用:调用时自动把"类对象"作为第一个参数传入。
@classmethod
def as_view(cls, **initkwargs):
# cls == View / RegisterView / LoginView
如果没有它,cls 不存在,self 又需要实例才能用 → 鸡生蛋问题。
② Django 在 URLConf 中只有"类",没有"实例"
path('register/', views.RegisterView.as_view())
URLConf 存的是 RegisterView(类对象),不是 RegisterView()(实例)。
只有类方法可以不依赖实例直接调用。
③ 保证多态正确性
class View:
@classmethod
def as_view(cls):
self = cls() # ← 不一定是 View,可能是任何子类
如果写成 self = View(),那 LoginView.as_view() 也会返回 View 实例 → 多态崩溃。
一句话记忆
"URLConf 只有图纸,没有房子;
@classmethod是唯一能拿着图纸去盖房子的工具。"
二、cls 和 self 的本质区别
问题起源
"
cls也是不是关键字,也是约定这样用?"
正确答案
✅ cls 和 self 一样,都不是 Python 关键字,是 PEP 8 强制约定的参数名。
| 对比项 | self |
cls |
|---|---|---|
| 是否关键字 | ❌ | ❌ |
| 约定来源 | PEP 8 | PEP 8 |
| 使用位置 | 实例方法 | 类方法 |
| 代表什么 | 实例本身 | 类本身 |
| 典型场景 | def get(self, request) |
@classmethod def as_view(cls) |
| 能否改名 | ✅ 语法允许 | ✅ 语法允许 |
| 是否建议改名 | ❌ 绝不 | ❌ 绝不 |
为什么叫 cls 不叫 class?
因为 class 是 Python 关键字,不能做参数名:
@classmethod
def as_view(class): # ❌ SyntaxError
pass
所以社区选了 cls(class 的缩写)。
一句话记忆
"
self是实例的约定名,cls是类的约定名。都不是关键字,但都比关键字还神圣。"
三、self = cls(**initkwargs) 的作用
问题起源
"这句代码的作用是什么?"
正确答案
这是 CBV 的"出生点" —— 从"图纸"变成"房子"的那一行。
self = cls(**initkwargs)
等价于:
self = RegisterView(**initkwargs)
Python 内部严格执行 3 步
第 1 步:cls.__new__(cls) → 分配内存(造空壳)
第 2 步:obj.__init__(**kwargs) → 初始化(装修)
第 3 步:return obj → 赋值给 self
为什么不用 RegisterView() 而要用 cls()?
如果用 RegisterView() |
用 cls() |
|---|---|
| 写死父类 | ✅ 动态绑定子类 |
LoginView.as_view() 返回 RegisterView 实例 |
✅ 返回 LoginView 实例 |
| 破坏继承体系 | ✅ 多态安全 |
此时还没有 request
| 状态 | 是否存在 |
|---|---|
cls |
✅ |
self |
✅(刚刚诞生) |
request |
❌(还没传进来) |
self.request |
❌ |
✅ 实例先创建 → request 后绑定 → setup() 才把它们连起来
一句话记忆
"这是按图纸盖房子的那一行代码。之前是类,之后是实例。"
四、getattr() 返回什么?
问题起源
"
getattr(self, request.method.lower())返回的是什么?"
正确答案
返回的是"方法对象"(函数的引用),不是调用结果。
handler = getattr(self, 'post')
# handler 是 self.post 这个方法本身
# 还没有调用!
return handler(request, *args, **kwargs)
# 这里才真正调用
类比
def greet(name):
return f"hello {name}"
func = greet # func 是函数对象,没有调用
result = func("张三") # 这里才调用,返回 "hello 张三"
dispatch() 完整逻辑
def dispatch(self, request, *args, **kwargs):
method = request.method.lower() # 'post'
if method not in self.http_method_names:
return self.http_method_not_allowed(request)
handler = getattr(self, method) # 拿到 self.post 方法
return handler(request, *args, **kwargs) # 调用它
一句话记忆
"
getattr()是'找到方法',括号()才是'执行方法'。两步分开,缺一不可。"
五、request 是谁传过来的?
问题起源
"View 类中很多方法的参数都有 request,request 是谁传过来的?"
正确答案
request 是 WSGI 服务器在 HTTP 请求到来时创建的,经由 Django 核心,最终由 view() 函数作为第一个参数传给你的 CBV 方法。
完整传递链
① WSGI 服务器(gunicorn / runserver)
→ 解析 HTTP
→ 创建 HttpRequest 对象
② Django 核心
→ URL 解析
→ 中间件处理
→ 找到对应的 view 函数
③ view(request, ...) ← request 首次出现
→ self = cls(**initkwargs) (创建实例)
→ self.setup(request) (绑定到 self.request)
→ self.dispatch(request) (继续传递)
④ dispatch(request)
→ handler = getattr(self, 'post')
→ handler(request) (传给 post)
⑤ def post(self, request): ← 你拿到 request
→ request.POST.get('username')
request 传递表
| 方法 | request 来源 | 谁传的 |
|---|---|---|
view(request) |
WSGI / Django | Django 核心 |
setup(self, request) |
view() |
view() |
dispatch(self, request) |
setup() |
setup() |
get(self, request) |
dispatch() |
dispatch() |
post(self, request) |
dispatch() |
dispatch() |
同一个 request 对象
# 在 view() 里
print(id(request)) # 140245678901248
# 在 post() 里
print(id(request)) # 140245678901248 ← 同一个!
❌ 不能在 __init__() 里用 request
def __init__(self, **kwargs):
print(self.request) # ❌ AttributeError
原因:__init__() 执行时 setup() 还没运行,self.request 还没赋值。
一句话记忆
"request 不是谁造的,是请求一来就递到你手上的。"
六、今日代码验证(View 最小实现)
from django.http import HttpResponse, HttpResponseNotAllowed
class View:
"""Django CBV 基类 · 最小教学版"""
http_method_names = ['get', 'post', 'put', 'delete']
# ① 类方法:Django 启动时执行,返回 view 函数
@classmethod
def as_view(cls, **initkwargs):
def view(request, *args, **kwargs):
# request 是 Django 核心传进来的
self = cls(**initkwargs) # 创建实例
self.setup(request, *args, **kwargs) # 绑定 request
return self.dispatch(request, *args, **kwargs)
return view
# ② 构造函数:保存配置参数
def __init__(self, **kwargs):
self.kwargs = kwargs
super().__init__()
# ③ setup:把 request 绑到实例
def setup(self, request, *args, **kwargs):
self.request = request # ← request 在这里"落地"
self.args = args
self.kwargs = kwargs
# ④ dispatch:按 HTTP 方法分发
def dispatch(self, request, *args, **kwargs):
method = request.method.lower()
if method not in self.http_method_names:
return self.http_method_not_allowed(request)
handler = getattr(self, method) # 找到 self.post / self.get
return handler(request, *args, **kwargs)
# ⑤ 405 错误处理
def http_method_not_allowed(self, request):
return HttpResponseNotAllowed(self.http_method_names)
# ⑥ 子类必须重写
def get(self, request, *args, **kwargs):
raise NotImplementedError
def post(self, request, *args, **kwargs):
raise NotImplementedError
# 使用示例
class RegisterView(View):
def get(self, request):
return HttpResponse("GET 注册页面")
def post(self, request):
username = request.POST.get('username')
return HttpResponse(f"注册用户:{username}")
七、今日常见误区
❌ 误区 1:as_view() 创建 request
# as_view() 执行时(Django 启动),还没有请求
# request 是后来 view() 被调用时才出现的
❌ 误区 2:getattr() 直接调用了方法
handler = getattr(self, 'post')
# handler 是方法对象,还没调用
# 必须 handler(request) 才执行
❌ 误区 3:cls 是关键字
import keyword
'cls' in keyword.kwlist # False → 不是关键字
八、今日金句
"CBV 不是魔法,而是 Python 面向对象 + 闭包的精密组合。
cls 是模具,self 是产品,dispatch 是调度室,get/post 是工人。"
九、今日学习总结
| 知识点 | 掌握程度 |
|---|---|
as_view() 为什么是 @classmethod |
✅ |
cls vs self 的本质区别 |
✅ |
self = cls(**initkwargs) 的 3 步执行过程 |
✅ |
getattr() 返回方法对象而非结果 |
✅ |
request 的完整传递链 |
✅ |
request 不能在 __init__() 中使用 |
✅ |
| View 最小实现(不含模板覆盖陷阱) | ✅ |
明日预告
进入 Python 对象模型深处:__new__ vs __init__ 到底谁是真正的构造函数?类方法里创建实例的根本目的是什么?
📌 Day 5 完 · 下一篇:Day6 Python 对象模型与构造机制

浙公网安备 33010602011771号