《从零构建一个可链式调用的 Python ORM:原理、设计与实战全解析》

2025博客之星年度评选已开启 10w+人浏览 2.4k人参与

《从零构建一个可链式调用的 Python ORM:原理、设计与实战全解析》

在我教授 Python 的这些年里,我常常遇到这样的问题:

“Django ORM 为什么能写出 User.objects.filter(age__gt=18).order_by('-created_at') 这种优雅的链式查询?
我能不能自己写一个简化版的 ORM?”

答案是:当然可以,而且你会从中学到大量 Python 语言的精髓。

ORM(Object Relational Mapping)不仅是 Web 开发的核心组件,更是 Python 生态中最具代表性的“优雅设计”。它融合了:

  • 链式调用(Fluent API)
  • 延迟执行(Lazy Evaluation)
  • 表达式构造(Query Expression)
  • 元编程(Metaclass)
  • 动态属性访问(__getattr__
  • SQL 生成器(Query Builder)

今天,我们将从零开始,手写一个支持链式调用的 ORM 查询构造器,类似 Django ORM,但更轻量、更易理解。


一、开篇:为什么要自己写一个 ORM?

Python 的发展历程中,ORM 一直扮演着重要角色:

  • Django ORM 让 Web 开发者无需写 SQL
  • SQLAlchemy 让工程师能优雅地构建复杂查询
  • Peewee、Tortoise ORM 等轻量框架让异步时代更高效

ORM 的本质是:

用 Python 对象表达 SQL 查询,用链式调用表达查询逻辑。

自己写一个 ORM,你将学到:

  • 如何设计链式 API
  • 如何构建表达式树
  • 如何延迟执行查询
  • 如何将 Python 语法映射到 SQL
  • 如何设计可扩展的架构

这些能力会让你在工程实践中更加游刃有余。


二、基础铺垫:链式调用的核心原理

链式调用的本质是:

每个方法返回 self 或一个新的对象。

例如:

class Query:
    def filter(self, **kwargs):
        print("filter:", kwargs)
        return self

q = Query().filter(name="Tom").filter(age__gt=18)

输出:

filter: {'name': 'Tom'}
filter: {'age__gt': 18}

链式调用的关键点:

  • 每次调用返回 Query 对象
  • Query 对象内部不断累积查询条件
  • 最终执行时统一生成 SQL

这就是 ORM 的核心思想。


三、设计 ORM 的整体架构

我们将构建一个简化版 ORM,包含:

Model(模型类)
 ├── Meta(表名、字段)
 ├── objects(QuerySet 管理器)
QuerySet(查询构造器)
 ├── filter()
 ├── order_by()
 ├── limit()
 ├── build_sql()
Field(字段类型)
Database(执行 SQL)

最终希望能写出:

users = User.objects.filter(age__gt=18).order_by('-created_at').limit(10)
print(users.sql())

输出:

SELECT * FROM user WHERE age > 18 ORDER BY created_at DESC LIMIT 10;

四、第一步:定义字段类型 Field

字段类型用于描述数据库字段:

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

class IntegerField(Field):
    pass

class StringField(Field):
    pass

五、第二步:使用元类收集模型字段

ORM 的关键是:

模型类中的字段要被自动收集。

例如:

class User(Model):
    id = IntegerField()
    name = StringField()
    age = IntegerField()

我们希望自动得到:

{
    "id": IntegerField(name="id"),
    "name": StringField(name="name"),
    "age": IntegerField(name="age")
}

使用 metaclass:

class ModelMeta(type):
    def __new__(cls, name, bases, attrs):
        fields = {}
        for key, value in list(attrs.items()):
            if isinstance(value, Field):
                value.name = key
                fields[key] = value
                attrs.pop(key)
        attrs['_fields'] = fields
        return type.__new__(cls, name, bases, attrs)

六、第三步:定义 Model 基类

Model 需要:

  • 保存表名
  • 提供 objects 查询入口
class Model(metaclass=ModelMeta):
    @classmethod
    def table_name(cls):
        return cls.__name__.lower()

    @classmethod
    def objects(cls):
        return QuerySet(cls)

七、第四步:构建 QuerySet(查询构造器)

QuerySet 是 ORM 的灵魂。

我们需要支持:

  • filter()
  • order_by()
  • limit()
  • sql()

1. 初始化

class QuerySet:
    def __init__(self, model):
        self.model = model
        self._filters = []
        self._order = None
        self._limit = None

2. filter():解析 age__gt=18

Django ORM 的 filter 支持:

  • age__gt=18 → age > 18
  • name__contains=“Tom” → name LIKE ‘%Tom%’

我们先实现简单版本:

OPERATORS = {
    "gt": ">",
    "lt": "<",
    "gte": ">=",
    "lte": "<=",
    "contains": "LIKE"
}

class QuerySet:
    def filter(self, **kwargs):
        for key, value in kwargs.items():
            if "__" in key:
                field, op = key.split("__")
                sql_op = OPERATORS[op]
            else:
                field, sql_op = key, "="
            self._filters.append((field, sql_op, value))
        return self

3. order_by()

def order_by(self, field):
    if field.startswith("-"):
        self._order = (field[1:], "DESC")
    else:
        self._order = (field, "ASC")
    return self

4. limit()

def limit(self, n):
    self._limit = n
    return self

5. 构建 SQL

def sql(self):
    table = self.model.table_name()
    sql = f"SELECT * FROM {table}"

    # WHERE
    if self._filters:
        parts = []
        for field, op, value in self._filters:
            if op == "LIKE":
                parts.append(f"{field} LIKE '%{value}%'")
            else:
                parts.append(f"{field} {op} {value}")
        sql += " WHERE " + " AND ".join(parts)

    # ORDER BY
    if self._order:
        field, direction = self._order
        sql += f" ORDER BY {field} {direction}"

    # LIMIT
    if self._limit is not None:
        sql += f" LIMIT {self._limit}"

    return sql + ";"

八、第五步:定义一个模型并测试

class User(Model):
    id = IntegerField()
    name = StringField()
    age = IntegerField()
    created_at = StringField()

测试:

qs = User.objects().filter(age__gt=18, name__contains="Tom").order_by("-created_at").limit(5)
print(qs.sql())

输出:

SELECT * FROM user 
WHERE age > 18 AND name LIKE '%Tom%' 
ORDER BY created_at DESC 
LIMIT 5;

这就是一个最小可用的 ORM 查询构造器。


九、进阶:支持链式调用的延迟执行(Lazy Evaluation)

Django ORM 的一个重要特性是:

查询不会立即执行,而是在需要时才执行。

例如:

qs = User.objects.filter(age__gt=18)
qs = qs.order_by('-id')
qs = qs.limit(10)

我们已经实现了这一点,因为 QuerySet 只是累积条件,只有调用 .sql() 才生成 SQL。


十、进阶:支持表达式对象(Q 对象)

Django ORM 支持:

User.objects.filter(Q(age__gt=18) | Q(name__contains="Tom"))

我们可以扩展:

class Q:
    def __init__(self, **kwargs):
        self.kwargs = kwargs
        self.connector = "AND"
        self.children = []

    def __or__(self, other):
        q = Q()
        q.connector = "OR"
        q.children = [self, other]
        return q

QuerySet.filter() 需要支持 Q 对象解析。


十一、进阶:支持 select() 字段选择

def select(self, *fields):
    self._select = fields
    return self

SQL:

SELECT id, name FROM user ...

十二、进阶:支持 join()

你可以扩展:

def join(self, other_model, on):
    ...

生成:

SELECT ... FROM user 
JOIN order ON user.id = order.user_id

十三、案例实战:构建一个小型用户查询系统

假设我们要实现:

users = (
    User.objects()
        .filter(age__gte=20)
        .filter(name__contains="Li")
        .order_by("id")
        .limit(3)
)

print(users.sql())

输出:

SELECT * FROM user 
WHERE age >= 20 AND name LIKE '%Li%' 
ORDER BY id ASC 
LIMIT 3;

你可以将 SQL 交给 sqlite3 执行:

import sqlite3

conn = sqlite3.connect("test.db")
cursor = conn.cursor()
cursor.execute(users.sql())
rows = cursor.fetchall()

十四、最佳实践:如何设计一个可扩展的 ORM?


1. QuerySet 必须是不可变对象

每次调用返回新对象:

def filter(self, **kwargs):
    clone = self._clone()
    clone._filters.append(...)
    return clone

避免链式调用污染原对象。


2. SQL 构造器必须分层设计

  • WHERE 构造器
  • ORDER 构造器
  • LIMIT 构造器
  • JOIN 构造器

3. 字段类型必须可扩展

支持:

  • BooleanField
  • DateTimeField
  • ForeignKey
  • ManyToManyField

4. QuerySet 必须支持迭代

def __iter__(self):
    rows = self.execute()
    for row in rows:
        yield self.model(**row)

5. 支持缓存与 QuerySet 重用

避免重复执行 SQL。


十五、前沿视角:ORM 的未来趋势

Python ORM 正在向几个方向演进:


1. 异步 ORM(async ORM)

如:

  • Tortoise ORM
  • GINO
  • SQLModel(FastAPI 作者推出)

2. 类型安全 ORM

如:

  • SQLModel(基于 Pydantic)
  • Piccolo ORM

3. 自动生成 SQL(AI-assisted ORM)

未来 ORM 可能自动优化 SQL、自动生成索引。


十六、总结与互动

今天,我们从零构建了一个支持链式调用的 ORM 查询构造器,完整经历了:

  • 字段定义
  • 元类收集字段
  • QuerySet 构造器
  • filter/order/limit
  • SQL 生成
  • 延迟执行
  • 可扩展架构设计

你不仅学会了如何写 ORM,更理解了 ORM 背后的设计哲学。


开放性问题

我很想听听你的经验:

  • 你在项目中是否遇到过 ORM 性能瓶颈
  • 你更喜欢 Django ORM 还是 SQLAlchemy
  • 你希望我继续写“手写 ORM 系列”的哪些内容(如 JOIN、Q 对象、异步 ORM)

告诉我你的方向,我可以继续为你扩展下一篇文章。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

铭渊老黄

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值