R / Richie全部文章 ↑

Python · 2 分钟阅读

ORM 实现原理

想真正吃透 ORM,建议先理解 Python 的两个底层机制:元类(metaclass)与描述符(descriptor)。

Model 自动获得 id 与 objects

Django 在你写 class Topic(models.Model) 时通过元类做了两件事:

  • 注入 id 自增主键
  • 注入 objects = Manager() 查询入口

这一切发生在类创建的瞬间,靠的就是 元类。

1. 元类

类的类型本身就是 type:

type(int)            # <class 'type'>
type(str)            # <class 'type'>
type(type)           # <class 'type'>

type 还能动态创建类:

# 三参数:名称、父类元组、属性字典
A = type("A", (), {"hello": "World"})

def fun(self):
    return "Hello"

B = type("B", (A,), {"fun": fun})

用元类自动改写类

class BBSMeta(type):
    def __new__(mcs, name, bases, attrs):
        attrs["desc"] = lambda self: "django bbs"
        return super().__new__(mcs, name, bases, attrs)

class A(metaclass=BBSMeta):
    pass

A().desc()    # "django bbs"

Python 创建类时的元类查找顺序:

  1. 类自身的 metaclass
  2. 父类中的 metaclass
  3. 模块中的 metaclass
  4. 最终回退到 type

Django 的 ModelBase 就是一个元类,它会在类创建时遍历 Field、生成 objects、补 id、注册到 apps。

2. 描述符

实现了 __get__ / __set__ / __delete__ 任意一个的类对象,称为描述符。

class Tracker:
    def __get__(self, instance, owner):
        return instance.__dict__.get(self.name)

    def __set__(self, instance, value):
        instance.__dict__[self.name] = value
        instance.save()   # 拦截赋值

Django 的 Field 是描述符家族:

协议 作用
__get__ 读取字段值(自动 SELECT)
__set__ 写入字段值(标记 dirty)

Django 5.x 的 DeferredAttribute(懒加载字段)就是一个非数据描述符的经典实现。

3. QuerySet 简述

QuerySet 内部维护一棵 查询表达式树,链式调用只是往树上挂节点;真正迭代时才编译成 SQL 并执行——这就是 惰性求值。

qs = Book.objects.filter(price__gt=50).order_by("-id")
# 此时不查数据库
list(qs)        # 此处触发 SQL

一句话总结

元类负责"在类诞生时做改造",描述符负责"在属性访问时做拦截"。两者合力,让 Model 既像普通 Python 类,又能映射到数据库表。