# PART07:Python 对象模型与构造机制
> 记录时间:2026年8月6日
> 环境:Fedora 44 + Python 3.12.13 + Django 5.2.16
---
## 一、今日核心问题溯源
在深入分析 `View` 类源码的过程中,触发了对 Python 底层对象模型的连环追问:
1. **构造函数之谜**:Python 的构造函数到底是什么?`__init__` 还是 `__new__`?
2. **实例化时机**:为什么 `cls()` 必须在请求到来时才执行,而不是 URL 加载时?
3. **无父类与 `__init__` 的关系**:没有显式继承父类时,是否还需要写 `__init__`?
4. **`*args` / `**kwargs` 的参数传递本质**:为什么 View 的 `__init__` 要写成 `def __init__(self, **kwargs)`?
5. **`getattr()` 返回的是什么**:`dispatch()` 里 `getattr(self, method)` 拿到的是方法对象还是执行结果?
这些问题不再是"如何用 Django",而是"Django 为何如此设计"以及"Python 底层如何支撑这种设计"。
---
## 二、关键认知突破
### 1️⃣ Python 的"双构造函数"机制
Python 中没有单一的构造函数,而是**两个阶段的精密配合**:
| 方法 | 角色 | 是否常重写 | 职责 |
|:---|:---|:---:|:---|
| `__new__(cls, ...)` | **构造器 (Constructor)** | ❌ 极少 | 分配内存,返回空对象 |
| `__init__(self, ...)` | **初始化器 (Initializer)** | ✅ 极频繁 | 填充属性,初始化状态 |
**调用链(铁律)**:
```python
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)` |
**核心代码**:
```python
@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
```
**设计优势**:
1. **避免过早初始化**:启动时不依赖请求数据,View 实例无法有意义地初始化。
2. **请求级隔离**:每次请求创建新实例,避免多线程/多请求间的状态污染。
3. **资源节约**:不是每个 URL 每次启动都需要实例化,按需创建。
---
### 3️⃣ 无父类与 `__init__` 的关系
**结论**:即使没有显式父类(除了隐式的 `object`),也可以不写 `__init__()`。
**原理**:
- Python 3 中,所有类最终都隐式继承自 `object`。
- `object` 提供了默认的 `__init__()`(空实现)。
- 如果子类不写 `__init__`,自动继承 `object.__init__()`。
```python
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 印证**:
```python
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`?
**答案**:**解耦 + 未来兼容**。
```python
def dispatch(self, request, *args, **kwargs):
...
```
| 形式 | 含义 | 作用 |
|------|------|------|
| `*args` | 收集多余的位置参数 | 捕获 URL 中的位置参数(如 `path('user/<int:pk>/', ...)`) |
| `**kwargs` | 收集多余的关键字参数 | 捕获 URL 中的命名参数、查询参数 |
**调用链中的传递**:
```python
# 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)` 拿到的是什么?
```python
handler = getattr(self, 'get') # ← 没有括号!
handler(request, *args, **kwargs) # ← 这里才调用
```
**真相**:
| 代码 | 返回 | 类型 |
|------|------|------|
| `getattr(self, 'get')` | `get` 方法对象 | `bound method` |
| `getattr(self, 'get')()` | `get()` 的返回值 | `HttpResponse` |
| `self.get` | `get` 方法对象 | `bound method` |
**完整流程**:
```python
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__` 的执行顺序
```python
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()` 的必要性
```python
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()` 返回方法对象
```python
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()` 本质**:明确了它返回方法对象而非执行结果,加 `()` 才是调用。
---