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
相关推荐
计算机毕业编程指导师1 小时前
【Python毕设选题推荐】基于Spark的宫颈癌变光谱特征数据分析可视化系统源码 毕业设计 选题推荐 毕设选题 数据分析 机器学习
大数据·hadoop·python·计算机·spark·毕业设计·宫颈癌变
FYKJ_20101 小时前
springboot心理健康管理系统86254-计算机课程设计、毕业设计
vue.js·spring boot·后端·python·mysql·django·课程设计
weixin_307779136 小时前
C++代码实现MATLAB中的dlarray函数功能
开发语言·c++·算法·matlab
一水鉴天9 小时前
映射、哈希表与哈斯图:计算机科学的三种基线 20261003(元宝)
开发语言·人工智能
Frank_refuel9 小时前
C++11之一场名为“搬家”的 C++ 之旅
开发语言·c++
海绵宝宝转agent9 小时前
learn-claude-code第1-5章开源学习笔记分享
人工智能·笔记·python·学习
小奇不哭10 小时前
ROS2 单节点,RGB-D 深度相机局部路径提取 + B 样条平滑路径 + YOLOv8 障碍物检测 + 深度测距,用于移动机器人视觉局部循迹。
python·yolov8·ros2·cv2·路径探索循迹
小小龙学IT10 小时前
Python scikit-learn 机器学习库深度解析
python·机器学习·scikit-learn
Wang's Blog10 小时前
Java 项目实战: 外卖平台优化-Nginx配置文件结构与块层级
java·开发语言·nginx