30、综合实战:从零构建一个迷你ORM框架
终于到了最后一章。说实话,前面讲了那么多封装、继承、多态、设计模式、元类,如果你觉得有点散,那这一章就是把这些珠子串起来的那根线。
我们要干一件什么事呢?从零写一个迷你ORM框架。ORM,全称Object-Relational Mapping,说白了就是把数据库表映射成Python对象。你操作对象,就等于操作数据库。Django的ORM、SQLAlchemy,底层核心思想都差不多。
我个人习惯,学一个新东西,一定要自己动手造一个轮子。哪怕很简陋,但造完之后,再看那些工业级框架,你就能看懂它们为什么要那么设计了。
30.1 整体架构设计
先想清楚我们要做什么。一个迷你ORM,至少要有这几个能力:
- 定义模型类,自动映射到数据库表
- 支持增删改查(CRUD)操作
- 字段类型自动校验
- 查询结果自动封装成对象
嗯,这里要注意,我们不搞复杂的关联查询、懒加载那些。核心是让你看清框架的骨架。
我画了一张架构图,你看一眼就明白了:
你看,从上到下四层。用户只管定义模型,底层自动帮你拼SQL、做映射。这就是封装的力量。
30.2 元类:自动注册模型
先解决第一个问题:怎么让一个类自动知道它对应哪张表?
用元类。我在项目中遇到过很多次,元类最适合干这种「类创建时自动做点手脚」的事。
class ModelMeta(type):
def __new__(cls, name, bases, attrs):
# 跳过 Model 基类本身
if name == 'Model':
return super().__new__(cls, name, bases, attrs)
# 自动生成表名:类名小写 + 's'
table_name = name.lower() + 's'
attrs['__table__'] = table_name
# 收集字段信息
fields = {}
for key, value in attrs.items():
if isinstance(value, Field):
fields[key] = value
attrs['__fields__'] = fields
return super().__new__(cls, name, bases, attrs)
这段代码干了三件事:
- 跳过基类,只处理用户定义的模型类
- 自动生成表名(比如 User → users)
- 收集所有 Field 类型的属性,存到 __fields__ 里
你想想看,如果没有元类,你得在每个模型类里手动写 table_name 和 fields 字典。有了元类,这些脏活累活全自动了。
30.3 字段描述符:类型校验
接下来是字段。每个字段都要能校验类型、处理默认值。这里用描述符(Descriptor)最合适。
class Field:
def __init__(self, default=None, nullable=True):
self.default = default
self.nullable = nullable
self.name = None # 会在元类中设置
def __set_name__(self, owner, name):
self.name = name
def validate(self, value):
raise NotImplementedError
class IntegerField(Field):
def validate(self, value):
if value is None and self.nullable:
return
if not isinstance(value, int):
raise TypeError(f'{self.name} 必须是整数,收到 {type(value).__name__}')
return value
class CharField(Field):
def __init__(self, max_length=255, **kwargs):
super().__init__(**kwargs)
self.max_length = max_length
def validate(self, value):
if value is None and self.nullable:
return
if not isinstance(value, str):
raise TypeError(f'{self.name} 必须是字符串')
if len(value) > self.max_length:
raise ValueError(f'{self.name} 长度不能超过 {self.max_length}')
return value
这里有个小细节:__set_name__ 是 Python 3.6 引入的,描述符可以自动知道自己在类里的属性名。我以前用旧写法还得手动传 name 参数,现在省事多了。
30.4 Model 基类:封装 CRUD
有了元类和字段,Model 基类就水到渠成了。它负责提供 save、delete、find 等方法。
class Model(metaclass=ModelMeta):
def __init__(self, **kwargs):
for name, field in self.__fields__.items():
value = kwargs.get(name, field.default)
setattr(self, name, value)
def save(self):
# 拼 INSERT 或 UPDATE SQL
fields = list(self.__fields__.keys())
values = [getattr(self, name) for name in fields]
if self.pk is None:
# INSERT
placeholders = ', '.join(['?' for _ in fields])
columns = ', '.join(fields)
sql = f"INSERT INTO {self.__table__} ({columns}) VALUES ({placeholders})"
cursor = db.execute(sql, values)
self.pk = cursor.lastrowid
else:
# UPDATE
set_clause = ', '.join([f"{name}=?" for name in fields])
sql = f"UPDATE {self.__table__} SET {set_clause} WHERE id=?"
db.execute(sql, values + [self.pk])
@classmethod
def find(cls, **kwargs):
# 拼 SELECT SQL
conditions = ' AND '.join([f"{k}=?" for k in kwargs.keys()])
sql = f"SELECT * FROM {cls.__table__} WHERE {conditions}"
rows = db.fetchall(sql, list(kwargs.values()))
return [cls(**row) for row in rows]
注意看 save 方法里的 pk 判断。如果主键是 None,说明是新记录,执行 INSERT;否则执行 UPDATE。这就是多态的一种体现——同一个方法,根据对象状态走不同逻辑。
核心思想:用户只需要操作 Python 对象,完全不用写 SQL。这就是 ORM 的终极目标——让数据库操作变得像操作普通对象一样自然。
30.5 使用示例
来看看最终效果:
class User(Model):
id = IntegerField(default=None, nullable=False)
name = CharField(max_length=50)
age = IntegerField(default=0)
# 创建用户
user = User(name='张三', age=25)
user.save() # 自动 INSERT
# 查询用户
users = User.find(name='张三')
for u in users:
print(u.name, u.age)
u.age = 26
u.save() # 自动 UPDATE
是不是很清爽?你完全感觉不到 SQL 的存在。这就是封装的力量——把复杂性藏在内部,对外只暴露简洁的接口。
30.6 避坑指南
我曾经在项目里用自己写的 ORM 踩过一个坑:没有处理连接池。每次 save 都新建数据库连接,高并发下直接打崩了数据库。后来加了个简单的连接池,问题才解决。
还有一点要注意:字段校验一定要在 setattr 时做,而不是在 save 时。否则你存了一个非法数据到对象里,等到 save 才发现,调试起来很痛苦。
小技巧:可以在 Field 描述符的 __set__ 方法里直接调用 validate,这样赋值时就校验,早发现早解决。
30.7 设计模式回顾
这个迷你 ORM 里用到了哪些设计模式?我帮你梳理一下:
| 设计模式 | 应用位置 | 作用 |
|---|---|---|
| 模板方法 | Model.save() | 定义 save 的骨架,子类只需提供字段 |
| 工厂方法 | Model.find() | 根据查询结果创建对象实例 |
| 策略模式 | 字段校验 | 不同字段类型有不同的校验策略 |
| 元类模式 | ModelMeta | 在类创建时自动注入表名和字段信息 |
你看,设计模式不是纸上谈兵。在实际框架里,它们无处不在。
30.8 总结
这一章,我们从零构建了一个迷你 ORM 框架。虽然简陋,但五脏俱全。你看到了:
- 元类如何自动注册模型信息
- 描述符如何做字段校验
- 封装如何隐藏 SQL 细节
- 多态如何让 save 方法自适应 INSERT/UPDATE
- 设计模式如何让代码更灵活
说实话,能坚持看到这里,你已经超越了绝大多数人。面向对象编程不是背概念,而是把这些思想内化成你的肌肉记忆。以后你写任何代码,都会不自觉地思考:这里能不能封装?那里能不能用多态?这个类要不要用元类?
嗯,这就是我想教给你的东西。
最后提醒:这个迷你 ORM 仅供学习,不要直接用于生产环境。生产环境请用 Django ORM、SQLAlchemy 等成熟框架。它们处理了太多你想象不到的边界情况。
公众号:蓝海资料掘金营,微信deep3321