Django + Vue 学习日志(07Python对象构造)
PART07:Python 对象模型与构造机制
记录时间:2026年8月6日
环境:Fedora 44 + Python 3.12.13 + Django 5.2.16
一、今日核心问题溯源
在深入分析 View 类源码的过程中,触发了对 Python 底层对象模型的连环追问:
- 构造函数之谜:Python 的构造函数到底是什么?
__init__还是__new__? - 实例化时机:为什么
cls()必须在请求到来时才执行,而不是 URL 加载时? - 无父类与
__init__的关系:没有显式继承父类时,是否还需要写__init__? *args/**kwargs的参数传递本质:为什么 View 的__init__要写成def __init__(self, **kwargs)?getattr()返回的是什么:dispatch()里getattr(self, method)拿到的是方法对象还是执行结果?
这些问题不再是"如何用 Django",而是"Django 为何如此设计"以及"Python 底层如何支撑这种设计"。
二、关键认知突破
1️⃣ Python 的"双构造函数"机制
Python 中没有单一的构造函数,而是两个阶段的精密配合:
| 方法 | 角色 | 是否常重写 | 职责 |
|---|---|---|---|
__new__(cls, ...) |
构造器 (Constructor) | ❌ 极少 | 分配内存,返回空对象 |
__init__(self, ...) |
初始化器 (Initializer) | ✅ 极频繁 | 填充属性,初始化状态 |
调用链(铁律):
instance = MyClass(*args, **kwargs)
# Python 解释器实际执行:
obj = MyClass.__new__(MyClass, *args, **kwargs) # 1️⃣ 分配内存
obj.__init__(*args, **kwargs) # 2️⃣ 填充属性
return obj
__new__负责"生":如果没它,对象根本不存在。通常用于元类、不可变对象(int、str、tuple)、单例模式。__init__负责"养":如果没它,对象是个空壳。99% 的业务逻辑在这里。
Django 印证:
View大量使用__init__来初始化self.kwargs,但几乎不碰__new__,因为 View 是可变对象,且不需要干预内存分配。
2️⃣ 实例化时机的设计意图:延迟创建
问题:为什么 as_view() 返回 view 函数,而不是在 URL 加载时就创建 View 实例?
原因分析:
| 时机 | 如果此时创建实例 | 实际做法 |
|---|---|---|
| URL 加载(启动期) | ❌ 没有 request,setup() 无法执行 |
只创建 view 函数 |
| 请求到来(运行期) | ✅ 有完整的 request 对象 |
在 view 中执行 self = cls(**initkwargs) |
核心代码:
@classmethod
def as_view(cls, **initkwargs):
def view(request, *args, **kwargs):
# 请求到来时才创建实例
self = cls(**initkwargs) # ← 这里才"生"
self.setup(request, *args, **kwargs) # ← 这里才"绑定"
return self.dispatch(request, *args, **kwargs)
return view
设计优势:
- 避免过早初始化:启动时不依赖请求数据,View 实例无法有意义地初始化。
- 请求级隔离:每次请求创建新实例,避免多线程/多请求间的状态污染。
- 资源节约:不是每个 URL 每次启动都需要实例化,按需创建。
3️⃣ 无父类与 __init__ 的关系
结论:即使没有显式父类(除了隐式的 object),也可以不写 __init__()。
原理:
- Python 3 中,所有类最终都隐式继承自
object。 object提供了默认的__init__()(空实现)。- 如果子类不写
__init__,自动继承object.__init__()。
class A:
pass # 没有 __init__,继承自 object.__init__()
class B:
def __init__(self, name):
self.name = name # 重写了 __init__,切断了默认行为
class C(B):
def __init__(self, name, age):
super().__init__(name) # 必须调用父类 __init__
self.age = age
Django 印证:
class View:
def __init__(self, **kwargs):
self.kwargs = kwargs
super().__init__() # ← 关键!调用 object.__init__()
class RegisterView(View):
def __init__(self, **kwargs):
self.extra = "something"
super().__init__(**kwargs) # 必须链式调用,否则 View.__init__ 不执行
关键心法:继承链中,每个
__init__都有责任调用super().__init__(),否则链条断裂,父类的初始化逻辑被跳过。
4️⃣ *args / **kwargs 的参数传递本质
问题:为什么 View 的方法签名里到处都是 *args, **kwargs?
答案:解耦 + 未来兼容。
def dispatch(self, request, *args, **kwargs):
...
| 形式 | 含义 | 作用 |
|---|---|---|
*args |
收集多余的位置参数 | 捕获 URL 中的位置参数(如 path('user/<int:pk>/', ...)) |
**kwargs |
收集多余的关键字参数 | 捕获 URL 中的命名参数、查询参数 |
调用链中的传递:
# as_view() 中的 view 函数
def view(request, *args, **kwargs):
self = cls(**initkwargs)
self.setup(request, *args, **kwargs) # ← 原样传递
return self.dispatch(request, *args, **kwargs) # ← 原样传递
# View 的方法签名
def setup(self, request, *args, **kwargs): ...
def dispatch(self, request, *args, **kwargs): ...
def get(self, request, *args, **kwargs): ...
✅ 每一层都不需要知道具体参数名
✅ 参数像水流一样从上往下传递
✅ 新增参数不需要修改每一层的签名
5️⃣ getattr() 返回的是方法对象,不是执行结果
问题:dispatch() 里 handler = getattr(self, method) 拿到的是什么?
handler = getattr(self, 'get') # ← 没有括号!
handler(request, *args, **kwargs) # ← 这里才调用
真相:
| 代码 | 返回 | 类型 |
|---|---|---|
getattr(self, 'get') |
get 方法对象 |
bound method |
getattr(self, 'get')() |
get() 的返回值 |
HttpResponse |
self.get |
get 方法对象 |
bound method |
完整流程:
def dispatch(self, request, *args, **kwargs):
method = request.method.lower() # 'get'
handler = getattr(self, method) # 拿到 self.get 方法对象
return handler(request, *args, **kwargs) # 调用 self.get()
关键心法:
getattr()是"拿到函数",加()才是"调用函数"。这和as_view()返回函数、URLConf 调用函数的逻辑完全一致。
三、代码验证(铁证级实验)
实验 1:验证 __new__ 与 __init__ 的执行顺序
class Demo:
def __new__(cls, *args, **kwargs):
print("1. __new__ called: 分配内存")
obj = super().__new__(cls)
print(f" 返回对象 id: {id(obj)}")
return obj
def __init__(self, name):
print("2. __init__ called: 初始化属性")
self.name = name
print(f" self.id: {id(self)}")
print("=== 开始创建实例 ===")
d = Demo("Test")
print(f"=== 创建完毕,d.name = {d.name} ===")
输出:
=== 开始创建实例 ===
1. __new__ called: 分配内存
返回对象 id: 140234567890
2. __init__ called: 初始化属性
self.id: 140234567890
=== 创建完毕,d.name = Test ===
✅ __new__ 先执行,返回的对象和 __init__ 中的 self 是同一个
实验 2:验证继承链中 super() 的必要性
class Parent:
def __init__(self):
print("Parent.__init__")
self.parent_attr = "from parent"
class Child(Parent):
def __init__(self):
print("Child.__init__ (没有调用 super)")
self.child_attr = "from child"
c = Child()
print(f"parent_attr: {getattr(c, 'parent_attr', '❌ 不存在!')}")
输出:
Child.__init__ (没有调用 super)
parent_attr: ❌ 不存在!
👉 忘记 super().__init__(),父类属性直接丢失!
实验 3:验证 getattr() 返回方法对象
class TestView:
def get(self):
return "GET response"
view = TestView()
# 不加括号:拿到方法对象
handler = getattr(view, 'get')
print(f"类型: {type(handler)}")
print(f"是否可调用: {callable(handler)}")
# 加括号:执行方法
result = handler()
print(f"调用结果: {result}")
输出:
类型: <class 'method'>
是否可调用: True
调用结果: GET response
四、今日与 Django 源码的串联
| Python 机制 | Django View 中的应用 |
作用 |
|---|---|---|
__new__ |
未重写(使用默认) | 内存分配 |
__init__ |
View.__init__(self, **kwargs) |
保存 URL 配置参数 |
super().__init__() |
链式调用 | 保障继承链完整 |
*args, **kwargs |
setup/dispatch/get/post 全链路 |
参数透传,解耦 |
getattr() |
dispatch() 中按方法名查找 |
动态调度 HTTP 方法 |
| 延迟实例化 | self = cls(**initkwargs) 在 view 中 |
请求级隔离 |
五、与前后日志的关联
| 日志 | 主题 | 与 PART06 的关系 |
|---|---|---|
| Day 4 | Request 对象与 QueryDict | request 从何而来?PART06 解释了它如何被绑定到 self.request |
| Day 5 | CBV 调度链(as_view → dispatch) |
PART06 解释了 cls() 为什么在 view 中执行,而非 URL 加载时 |
| PART07 | 闭包与装饰器 | as_view() 返回 view 闭包,PART06 解释了闭包之外的对象模型部分 |
六、常见误区澄清
| 误区 | 正解 |
|---|---|
__init__ 是构造函数 |
__init__ 是初始化器,__new__ 才是构造器(分配内存) |
没有父类就不用写 __init__ |
可以不写,但继承体系下必须 super().__init__() |
getattr(self, 'get') 会调用 get() |
返回方法对象,加 () 才调用 |
| URL 加载时创建了 View 实例 | URL 加载只执行 as_view()(返回函数),请求到来才创建实例 |
*args, **kwargs 是随便写的 |
是为了解耦参数传递,让每一层不需要知道具体参数名 |
cls() 和 self = cls() 是一回事 |
cls() 是调用类创建实例,self 是接收这个实例的变量名 |
七、今日金句
"类是图纸,
__new__是打地基,__init__是装修。URLConf 只负责下单(调用as_view()),工程师在客户上门(请求到来)时才现场盖房。"
"继承链中的
super().__init__()不是礼貌,而是责任——你断了链,父类的属性就永远丢了。"
八、学习总结
- 对象创建:明确了
__new__(生)与__init__(养)的职责边界和执行顺序。 - 实例化时机:理解了为什么 View 实例必须在请求到来时才创建(延迟初始化 + 请求级隔离)。
- 继承链责任:掌握了
super().__init__()在继承体系中的必要性。 - 参数透传:理解了
*args, **kwargs在 CBV 调度链中的解耦作用。 getattr()本质:明确了它返回方法对象而非执行结果,加()才是调用。

浙公网安备 33010602011771号