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 也是不是关键字,也是约定这样用?"

正确答案

clsself 一样,都不是 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 对象模型与构造机制

posted @ 2026-08-07 16:42  YiYezc  阅读(1)  评论(0)    收藏  举报