三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

Django + Vue 学习日志(07Python对象构造)

Django + Vue 学习日志(07Python对象构造)
# 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()` 本质**:明确了它返回方法对象而非执行结果,加 `()` 才是调用。

---
← 返回列表