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) ✅ 极频繁 填充属性,初始化状态

调用链(铁律)

instance = MyClass(*args, **kwargs)
# Python 解释器实际执行:
obj = MyClass.__new__(MyClass, *args, **kwargs)  # 1️⃣ 分配内存
obj.__init__(*args, **kwargs)                    # 2️⃣ 填充属性
return obj
  • __new__ 负责"生":如果没它,对象根本不存在。通常用于元类、不可变对象(intstrtuple)、单例模式。
  • __init__ 负责"养":如果没它,对象是个空壳。99% 的业务逻辑在这里。

Django 印证View 大量使用 __init__ 来初始化 self.kwargs,但几乎不碰 __new__,因为 View 是可变对象,且不需要干预内存分配。


2️⃣ 实例化时机的设计意图:延迟创建

问题:为什么 as_view() 返回 view 函数,而不是在 URL 加载时就创建 View 实例?

原因分析

时机 如果此时创建实例 实际做法
URL 加载(启动期) ❌ 没有 requestsetup() 无法执行 只创建 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

设计优势

  1. 避免过早初始化:启动时不依赖请求数据,View 实例无法有意义地初始化。
  2. 请求级隔离:每次请求创建新实例,避免多线程/多请求间的状态污染。
  3. 资源节约:不是每个 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_viewdispatch 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() 本质:明确了它返回方法对象而非执行结果,加 () 才是调用。

posted @ 2026-08-08 23:49  YiYezc  阅读(5)  评论(0)    收藏  举报