Django + Vue 学习日志(06Python 对象相关)

PART06:Python 对象模型、构造机制与类方法设计意图

记录时间:2026年8月6日
环境:Fedora 44 + Python 3.12.13 + Django 5.2.16


今日前言

今天的问题层级明显"升维"了 —— 不再局限于 Django,而是直击 Python 语言本身的设计哲学

问题清单:

  1. 没有父类,是不是必须写 __init__()
  2. 创建实例时参数怎么传给属性?能用 *args / **kwargs 吗?
  3. self / cls 是 Python 关键字吗?
  4. Python 的构造函数到底是什么?
  5. 类方法里为什么要创建实例?

每一个问题,都在把"会用"推向"懂原理"。


一、没有父类,是否必须写 init()?

问题起源

"如果没有父类,是不是这个类必须要写 __init__()?"

正确答案

不需要。 即使没有任何显式父类,类也可以不写 __init__()

原因

Python 3 中,所有类隐式继承自 object

class Foo:        # 等价于 class Foo(object):
    pass

object 已经提供了默认的 __init__()

class object:
    def __init__(self):
        pass  # 什么也不做

可以不写的场景

场景 示例
纯工具类(只有静态方法) MathUtils.add(a, b)
属性在别处动态添加 t = Temp(); t.x = 10
继承体系已满足需求 class MyView(View): pass

必须写的场景

场景 原因
需要初始化实例属性 self.name = name
需要校验参数 if age < 0: raise ValueError
需要组合其他对象 self.items = []

MRO 兜底验证

class A:
    pass

class B(A):
    pass

b = B()  # MRO: B → A → object → object.__init__()

✅ 无论怎么写,最终都会落到 object.__init__()

一句话记忆

"__init__() 不是类的必需品,而是实例状态的初始化器。"


二、创建实例时的参数传递

问题起源

"类后面 () 中的参数是按照 __init__() 中参数顺序传递给属性的么?
是不是也可以用 *args, **kwargs 传递很多个参数?"

正确答案

是的。 参数会原封不动地传给 __init__()

基础情况(位置参数 → 属性)

class User:
    def __init__(self, name, age):
        self.name = name
        self.age = age

u = User('张三', 18)
# '张三' → name
# 18 → age

*args 接收任意位置参数

class User:
    def __init__(self, *args):
        self.args = args

u = User('张三', 18, '北京')
# self.args == ('张三', 18, '北京')

✅ 打包成元组,不关心数量,不关心名字
❌ 可读性差

**kwargs 接收任意关键字参数

class User:
    def __init__(self, **kwargs):
        self.name = kwargs.get('name')
        self.age = kwargs.get('age')

u = User(name='张三', age=18)

✅ 打包成字典,参数名 = key

*args + **kwargs 组合(Django 最爱)

class User:
    def __init__(self, name, *args, **kwargs):
        self.name = name
        self.extra_args = args
        self.extra_kwargs = kwargs

u = User('张三', 18, '北京', city='上海', job='dev')
# name = '张三'
# extra_args = (18, '北京')
# extra_kwargs = {'city': '上海', 'job': 'dev'}

Django View 的真实写法

class View:
    def __init__(self, **kwargs):
        self.kwargs = kwargs
        super().__init__()

# URLConf 中
RegisterView.as_view(msg='hello')
# → self.kwargs == {'msg': 'hello'}

⚠️ 关键规则

参数类型 必须出现在签名中 能动态使用
位置参数 ✅ 是
*args ❌ 否
**kwargs ❌ 否

❌ 常见错误

class User:
    def __init__(self, **kwargs):
        self.name = name  # ❌ name 未定义!

✅ 正确:

self.name = kwargs['name']
# 或
self.name = kwargs.get('name')

一句话记忆

"参数是原材料,__init__() 是装配线,属性是成品。*args/**kwargs 只是收集器,属性必须手动赋值。"


三、self 和 cls 是不是关键字?

问题起源

"self 是 Python 关键字么?"
"cls 也是不是关键字,也是约定这样用?"

正确答案

都不是关键字,都是 PEP 8 强制约定的参数名。

铁证

import keyword
print('self' in keyword.kwlist)  # False
print('cls' in keyword.kwlist)   # False
print('class' in keyword.kwlist)  # True ← 这才是关键字

为什么叫 cls 不叫 class

因为 class 是关键字,不能做参数名:

@classmethod
def as_view(class):  # ❌ SyntaxError
    pass

所以社区选了 cls(class 的缩写)。

改名合法但不推荐

class User:
    def greet(this):       # ✅ 语法合法
        print("hello")

    @classmethod
    def create(klass):     # ✅ 语法合法
        return klass()

✅ Python 不在乎名字
❌ 社区会在乎(你会被同事打)

Python 的设计哲学

"显式优于隐式。"

如果 self 是关键字,Python 就得自动注入实例:

def greet(self):  # self 自动注入

Python 选择让你显式写出来,更清晰、更灵活。

对称美

名字 是否关键字 约定位置 代表
self 实例方法 实例本身
cls 类方法 类本身
this (Java/C++ 用)
class 语言保留

一句话记忆

"selfcls 是约定俗成的名字,不是语言赋予的身份。
不是强制的,但比强制的还严格。"


四、Python 的构造函数是什么?

问题起源

"Python 的构造函数是什么?"

正确答案

Python 没有"一个"构造函数,而是两个:

方法 角色 是否常重写
__new__() 构造器(Constructor)—— 分配内存 ❌ 极少
__init__() 初始化器(Initializer)—— 设属性 ✅ 极频繁

调用顺序(铁律)

obj = MyClass(1, 2)
# 内部执行:
# ① MyClass.__new__(MyClass, 1, 2) → 创建空对象
# ② obj.__init__(1, 2)             → 填充属性
# ③ return obj

代码证明

class Demo:
    def __new__(cls, *args, **kwargs):
        print("1. __new__ 被调用")
        return super().__new__(cls)

    def __init__(self, *args, **kwargs):
        print("2. __init__ 被调用")

d = Demo()
# 输出:
# 1. __new__ 被调用
# 2. __init__ 被调用

为什么分成两个?

① 不可变对象必须靠 __new__

class PositiveInt(int):
    def __new__(cls, value):
        return super().__new__(cls, abs(value))

PositiveInt(-5)  # 5

__init__ 改不了不可变对象
✅ 必须在 __new__ 里改

② 单例模式

class Singleton:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

✅ 只有 __new__ 能控制"是否创建新对象"

__init__ 的限制

class Demo:
    def __init__(self):
        return 123  # ❌ TypeError: __init__ should return None

__init__ 必须返回 None
✅ 返回值会被 Python 忽略并报错

Django 中的使用

__new__ __init__
View self.kwargs = kwargs
Model self._state = ModelState()
Field 偶尔

一句话记忆

"__new__ 负责生,__init__ 负责养。生在前,养在后。"


五、类方法里为什么要创建实例?

问题起源

"在类方法里面,为什么要创建这个实例,目的是什么?"

正确答案

类方法 = 工厂,实例 = 产品。工厂的目的就是生产产品。

目的 1:封装创建逻辑

class RegisterView:
    @classmethod
    def as_view(cls, **initkwargs):
        def view(request):
            self = cls(**initkwargs)   # 创建逻辑集中在这里
            self.setup(request)
            return self.dispatch(request)
        return view

✅ 创建逻辑只写一次
✅ 调用者(Django)不需要懂
✅ 以后改创建逻辑只改一处

目的 2:保证多态正确性

class View:
    @classmethod
    def as_view(cls):
        self = cls()  # ← 不一定是 View,是谁调用就创建谁

LoginView.as_view() → 创建 LoginView 实例
✅ 写死 View() 就破坏继承

目的 3:控制创建时机

阶段 能否创建实例
Django 启动
URLConf 加载
HTTP 请求到来

原因:请求没来就没有 requestself.request 无法赋值。

目的 4:统一参数注入

RegisterView.as_view(msg='hi')
# initkwargs = {'msg': 'hi'}
# cls(**initkwargs) → self.kwargs = {'msg': 'hi'}

✅ 类级配置安全注入实例
✅ 调用者不需要关心参数怎么用

工厂比喻

角色 对应
🏭 工厂
类方法 🏭 工厂的接单窗口
cls() 🏭 生产线启动
实例 📦 产品
调用者(Django) 🧑 客户

一句话记忆

"类方法存在的意义,是把'创建权'收回到类自己手中。"


六、今日代码验证(完整 View 最小版)

class View:
    http_method_names = ['get', 'post', 'put', 'delete']

    # 类方法:Django 启动时执行
    @classmethod
    def as_view(cls, **initkwargs):
        def view(request, *args, **kwargs):
            # ① 创建实例(__new__ + __init__)
            self = cls(**initkwargs)

            # ② 绑定请求
            self.setup(request, *args, **kwargs)

            # ③ 调度
            return self.dispatch(request, *args, **kwargs)
        return view

    # 构造:保存配置
    def __init__(self, **kwargs):
        self.kwargs = kwargs
        super().__init__()

    # 绑定请求到实例
    def setup(self, request, *args, **kwargs):
        self.request = request
        self.args = args
        self.kwargs = kwargs  # ⚠️ 覆盖了 __init__ 里的 kwargs!

    # 按 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)
        return handler(request, *args, **kwargs)

    def http_method_not_allowed(self, request):
        from django.http import HttpResponseNotAllowed
        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 __init__(self, msg=None, **kwargs):
        self.msg = msg  # ✅ 单独保存,防止被 setup 覆盖
        super().__init__(**kwargs)

    def get(self, request):
        return f"GET - {self.msg}"

    def post(self, request):
        username = request.POST.get('username')
        return f"POST - {username} - {self.msg}"

七、今日常见误区

❌ 误区 1:认为 self 是关键字

import keyword
'self' in keyword.kwlist  # False → 不是关键字

❌ 误区 2:认为 __init__ 是唯一的构造函数

# __new__ 才是真正的构造函数(分配内存)
# __init__ 只是初始化器

❌ 误区 3:类方法不需要创建实例

# 类方法的核心目的恰恰是创建实例
# 不创建实例,CBV 就永远只是"图纸"

❌ 误区 4:*args/**kwargs 会自动变成属性

def __init__(self, **kwargs):
    pass

u = User(name='张三')
u.name  # ❌ AttributeError

✅ 必须手动赋值:self.name = kwargs['name']


八、今日金句

"Python 的优雅不在于隐藏复杂度,而在于把每一步都摊开给你看。
__new__ 生,__init__ 养,cls 造,self 用 —— 没有魔法,只有约定。"


九、今日学习总结

知识点 掌握程度
无父类时 __init__() 非必须(object 兜底)
位置参数 / *args / **kwargs 传递机制
self / cls 非关键字,是 PEP 8 约定
__new__ vs __init__ 分工
类方法创建实例的 4 个目的
Django View 完整最小实现

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