AI全栈实战 | 3.3-01 Python OOP:__init__ 真的是构造函数吗,元类怎么让 Django Model 变魔法

上篇回顾 :3.2-01 把 Python 四大容器、推导式、函数一等公民、EAFP、鸭子类型讲透。本篇进入 Python 面向对象------与 Java OOP 表面相似但底层机制截然不同。理解魔法方法和元类,才能看懂 Django/SQLAlchemy 这类框架的「声明式魔法」。

一、开篇:Django Model 的魔法

python 复制代码
# Django Model
class User(models.Model):
    name = models.CharField(max_length=100)
    age = models.IntegerField()

# 使用
user = User(name="Tom", age=18)
user.save()
User.objects.filter(age__gte=18)

后端工程师看这段代码会困惑:

  1. name 和 age 是类属性还是实例属性?
  2. User.objects 哪来的?
  3. 为什么定义类属性就能对应数据库列?

答案是元类(metaclass)------Django 的 ModelBase 元类在类创建时拦截字段定义,把它们转成数据库列映射。这是 Python OOP 最强大的特性之一。

本篇从基础 OOP 讲到魔法方法再到元类,让你理解 Python 式面向对象与 Java 式 OOP 的根本差异。

二、类与实例

2.1 基本定义

python 复制代码
class Dog:
    # 类属性(所有实例共享)
    species = "Canis familiaris"

    # 初始化方法
    def __init__(self, name, age):
        self.name = name   # 实例属性
        self.age = age

    # 实例方法
    def bark(self):
        return f"{self.name} says Woof!"

dog = Dog("Rex", 3)
dog.bark()   # "Rex says Woof!"

2.2 与 Java 的区别

维度 Python Java
类属性/实例属性 共存于 class body static/instance 分明
构造 new + init 构造方法
访问控制 无(约定 _private/__private) private/protected/public
多继承 支持 不支持(接口多实现)

三、init 真的是构造函数吗

3.1 new 才是真正的构造

python 复制代码
class MyClass:
    def __new__(cls, *args, **kwargs):
        print("__new__:创建实例")
        instance = super().__new__(cls)
        return instance

    def __init__(self, value):
        print("__init__:初始化实例")
        self.value = value

obj = MyClass(42)
# 输出:
# __new__:创建实例
# __init__:初始化实例

流程:

  1. __new__ 创建实例(分配内存)
  2. __init__ 初始化实例(设置属性)

__new__ 是类方法(第一个参数是 cls),__init__ 是实例方法(第一个参数是 self)。

3.2 什么时候用 new

场景 说明
单例模式 new 里检查是否已创建
不可变类型 int/str/tuple 的子类化
元类 控制类的创建
python 复制代码
# 单例
class Singleton:
    _instance = None

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

a = Singleton()
b = Singleton()
a is b   # True

3.3 Java 构造函数 vs Python new /init

Java 的构造函数同时负责「创建+初始化」。Python 分两步:__new__ 创建、__init__ 初始化。分开的好处是可以在创建阶段做控制(单例、缓存实例等)。

四、继承与 MRO

4.1 单继承

python 复制代码
class Animal:
    def speak(self):
        return "..."

class Dog(Animal):
    def speak(self):
        return "Woof"

Dog().speak()   # "Woof"(子类覆盖)

4.2 多继承

python 复制代码
class Flyable:
    def fly(self):
        return "flying"

class Swimmable:
    def swim(self):
        return "swimming"

class Duck(Flyable, Swimmable):
    pass

duck = Duck()
duck.fly()   # "flying"
duck.swim()  # "swimming"

Java 不支持多继承(避免菱形问题),Python 支持但用 MRO 解决冲突。

4.3 MRO(方法解析顺序)

python 复制代码
class A:
    def greet(self): print("A")

class B(A):
    def greet(self): print("B")

class C(A):
    def greet(self): print("C")

class D(B, C):
    pass

D().greet()   # "B"(MRO: D → B → C → A)
print(D.__mro__)
# (<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>)

Python 用 C3 线性化算法计算 MRO------保证子类在父类前、多个父类按声明顺序查找。

4.4 菱形继承怎么解

复制代码
    A
   / \
  B   C
   \ /
    D

B 和 C 都继承 A。D 继承 B 和 C。如果 B 和 C 都重写了 A 的方法,D 调哪个?

Python 的 MRO 保证:D → B → C → A,先找到 B 的就用 B 的。如果 B 想调 A 的原始方法,用 super():

python 复制代码
class B(A):
    def greet(self):
        super().greet()   # 调 C 的 greet(MRO 下一个)
        print("B")

super() 在多继承下不是「调父类」而是「调 MRO 的下一个」。

五、property:受控属性

python 复制代码
class Temperature:
    def __init__(self, celsius):
        self._celsius = celsius

    @property
    def celsius(self):
        return self._celsius

    @celsius.setter
    def celsius(self, value):
        if value < -273.15:
            raise ValueError("低于绝对零度")
        self._celsius = value

    @property
    def fahrenheit(self):
        return self._celsius * 9/5 + 32

t = Temperature(25)
t.celsius = 30      # 调 setter
t.fahrenheit        # 86.0(只读 property)
t.celsius = -300    # ValueError

property 让属性的访问像直接访问字段,但背后可以有校验/计算逻辑。

六、高频魔法方法

6.1 字符串表示

python 复制代码
class User:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __repr__(self):
        return f"User(name={self.name!r}, age={self.age})"

    def __str__(self):
        return f"{self.name} ({self.age}岁)"

user = User("Tom", 18)
repr(user)   # User(name='Tom', age=18)(调试用)
str(user)    # Tom (18岁)(print 用)

6.2 可调用对象

python 复制代码
class Multiplier:
    def __init__(self, factor):
        self.factor = factor

    def __call__(self, x):
        return x * self.factor

double = Multiplier(2)
double(5)   # 10(像函数一样调用)

6.3 序列协议

python 复制代码
class Matrix:
    def __init__(self, data):
        self.data = data

    def __getitem__(self, key):
        return self.data[key]

    def __setitem__(self, key, value):
        self.data[key] = value

    def __len__(self):
        return len(self.data)

m = Matrix([1, 2, 3])
m[0]       # 1
m[0] = 10
len(m)     # 3

6.4 上下文管理器

python 复制代码
class FileManager:
    def __init__(self, filename, mode):
        self.filename = filename
        self.mode = mode

    def __enter__(self):
        self.file = open(self.filename, self.mode)
        return self.file

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.file.close()
        return False

with FileManager("test.txt", "w") as f:
    f.write("hello")
# 自动关闭

6.5 魔法方法速查表

方法 触发 用途
init 创建后 初始化
new 创建时 控制创建
repr repr() 调试表示
str str()/print 用户表示
call obj() 可调用
getitem objkey 索引访问
len len(obj) 长度
enter /exit with 上下文管理
eq == 相等比较
hash hash() 哈希
iter /next for 迭代

七、dataclass:现代化写法

python 复制代码
from dataclasses import dataclass, field

@dataclass
class User:
    name: str
    age: int = 0
    tags: list = field(default_factory=list)

    def __post_init__(self):
        if self.age < 0:
            raise ValueError("年龄不能为负")

user = User("Tom", 18)
# 自动生成 __init__/__repr__/__eq__

dataclass 省去了样板代码,类似 Java 的 record(JDK 14+)。

八、元类:控制类的创建

8.1 type 是所有类的元类

python 复制代码
# type 创建类
Dog = type("Dog", (object,), {"bark": lambda self: "Woof"})

dog = Dog()
dog.bark()   # "Woof"

type 既是函数又是类------它是所有类的元类。

8.2 自定义元类

python 复制代码
class MyMeta(type):
    def __new__(mcs, name, bases, namespace):
        # 类创建时拦截
        cls = super().__new__(mcs, name, bases, namespace)
        # 可以修改类
        return cls

class MyClass(metaclass=MyMeta):
    pass

8.3 Django Model 的元类魔法

python 复制代码
# 简化版 Django Model 元类
class ModelMeta(type):
    def __new__(mcs, name, bases, namespace):
        fields = {}
        for key, value in list(namespace.items()):
            if isinstance(value, Field):
                fields[key] = value
                value.name = key   # 字段知道自己叫什么
        namespace['_fields'] = fields
        namespace['objects'] = QuerySet()
        return super().__new__(mcs, name, bases, namespace)

class Field:
    def __init__(self, **kwargs):
        self.name = None
        self.options = kwargs

class CharField(Field): pass
class IntegerField(Field): pass

class Model(metaclass=ModelMeta):
    pass

# 使用
class User(Model):
    name = CharField(max_length=100)
    age = IntegerField()

User._fields    # {'name': CharField(...), 'age': IntegerField(...)}
User.objects    # QuerySet 实例

魔法揭秘:

  1. 定义 User 类时,ModelMeta.new 被调用
  2. 元类扫描命名空间,找出所有 Field 实例
  3. 把字段名写入 Field.name(让字段知道自己叫什么)
  4. 自动添加 objects 属性(QuerySet)

这就是 ORM 的核心------用元类把「声明式字段定义」转成「数据库列映射」。

九、小结表

概念 Python Java
构造 new + init 构造方法
多继承 支持(MRO) 不支持
属性控制 @property getter/setter
魔法方法 repr /call 等 toString() 等
元类 type/metaclass 无
数据类 @dataclass record
相关推荐
天外飞雨道沧桑8 分钟前
我在浏览器里练了一个 792 参数的大模型,它学会了补全 JavaScript
开发语言·javascript·ai·ecmascript·ai编程
hhhjhl12 分钟前
Yup 跨端三方库仓颉适配全流程
java·开发语言
运维行者_24 分钟前
网络监控工具怎么选?从设备监控到智能运维的能力清单
运维·服务器·开发语言·网络·人工智能·支持向量机·php
KeepRobots44 分钟前
11-01 温哥华不回拨:用 60 行 Python 查你的工单系统会不会差一小时(KeepRobots 工程笔记)
python
xiao77mwq1 小时前
你以为 OCR 慢?实测 81.3 s 里全是故障代价:如何把「定格态」消掉,让耗时从 6.5 s 到 13 倍差距显出原形
python
深圳市益普科技有限公司1 小时前
半导体良率管理YMS:良率与设备参数的相关性,先拉哪几组数据
人工智能·python·算法
“AI国潮设计-小江”1 小时前
【AIGC实战】Python+SDXL打造“潮汕英歌舞IP”万能立牌插件,批量生成“发财元宝蛋糕”视觉营销方案(附源码)
开发语言·人工智能·python·prompt·aigc
lhbweilai2 小时前
分析FreeRTOS内核对象API的源码实现(八)
c语言·开发语言·stm32·单片机
努力努力再努力wz2 小时前
【C++高阶系列】C++20 协程底层原理:函数为什么能暂停?从状态机到 Task,彻底串起挂起、恢复与协程调度
开发语言·c++·c++20·1024程序员节
咩咩啃树皮2 小时前
JavaScript 中 this 指向为什么总在变?
开发语言·前端·javascript