Python元类实战,通过元类实现数据库ORM框架

今天是Python专题的第19篇文章,我们一起来用元类实现一个简易的ORM数据库框架 。
本文主要是受到了廖雪峰老师Python3入门教程的启发,不过廖老师的博客有些精简,一些小白可能看起来比较吃力 。我在他的基础上做了一些补充和注释,尽量写得浅显一些 。
 ORM框架是什么如果是没有做过后端的小伙伴上来估计会有点蒙,这个ORM框架究竟是什么?ORM框架是后端工程师常用的一个框架,它的英文全称是Object Relational MApping,即对象-关系映射框架 。顾名思义就是把关系转化成对象的框架,关系这个词我们在哪里用的最多呢?
显然应该是数据库 。之前我们在分布式的文章介绍关系型数据库和非关系型数据库的时候就着重介绍过关系的含义 。我们常用的MySQL就是经典的关系型数据库,它存储的形式是表,但是表承载的数据其实是两个实体之间的"关系" 。比如学生上课这个场景,学生和课程是两个主体(entity),我们要记录的是这两个主体之间的关系,也就是学生上课这件事 。
而ORM框架做的事情是将这些关系映射成类,这样我们可以将这张表当中增删改查的功能抽象成类当中的方法 。这样我们就可以通过调用类的方式来操作数据库了,从而达到高度抽象业务逻辑、降低用户使用难度的目的 。
比如JAVA后端工程师常用的hibernate和ibatis都是用来做这件事情的,明确了框架的功能之后,我们先来设想一下最后的成果 。假设我们现在开发出来了这么一套框架,那么它用起来的感觉应该是怎样的?
我们来看下廖老师博客里给的例子:
class User(Model):# 定义类的属性到列的映射:id = IntegerField('id')name = StringField('username')email = StringField('email')password = StringField('password')User类代表了数据库当中的一张表,它有4个字段:id, name, email和password,我们在定义字段的同时也通过类别指定了它们的类型 。这个应该不难理解,上面的这个类等价于我们在数据库当中执行了这么一段建表的SQL:
create table if not exists user ( id int,name string,email string,password string)我们定义了表字段之后,接下来要做的就是根据字段创建数据了,其实也就是根据类创建实例 。我们希望User类型的实例就对应User表当中的一条记录,并且我们可以通过调用实例当中的方法,来操作这张表进行增删改查 。
# 创建一个实例:u = User(id=12345, name='Michael', email='test@orm.org', password='my-pwd')# 保存到数据库:u.save()那么,我们怎样可以实现这样的功能呢?
 功能实现我们先从简单的功能开始实现,首先是Field类,Field类表示数据库表当中一个字段的类型 。这里的逻辑很容易理清楚,我们需要定义多种类型,比如IntegerField和StringField 。我们可以对这些field类抽象出一个父类来:
class Field(object):def __init__(self, name, column_type):self.name = nameself.column_type = column_typedef __str__(self):return '<{}:{}>'.format(self.__class__.__name__, self.name)__str__方法当中打印出来的两个字段,分别是类别的名称和字段的名称,这段代码应该不难理解 。
接着,我们实现它的两个子类,分别是IntegerField和StringField:
class StringField(Field):def __init__(self, name):super(StringField, self).__init__(name, 'varchar(100)')class IntegerField(Field):def __init__(self, name):super(IntegerField, self).__init__(name, 'bigint')这里也不难理解,只是一个简单的继承应用而已 。
【Python元类实战,通过元类实现数据库ORM框架】接下来就到了最关键的部分,也就是Model类的实现 。我们先来分析一下我们希望Model这个类拥有的功能,由于它是我们定义出来的每一张表的父类,所以它应该能够获取子类当中的字段,并且将它存放在一个容器当中 。由于我们需要存储的是字段名和类型的映射,所以将它存储在dict当中比较合理 。
另外一个功能是我们希望它能够提供增删改查的接口,能够根据子类当中定义的字段自动生成相应的SQL语句去调用数据库 。这个也是ORM框架的意义所在 。
第二个功能容易实现,只要第一个功能搞定了,做一下字符串处理即可 。但是第一个功能有些麻烦,它也是元类的意义所在 。因为父类当中的方法是无法获取子类中定义的类属性的,只能通过元类,在构建类的时候可以拿到属性的信息 。
所以我们已经很明确了,我们实现元类的目的就是为了实现这个功能 。理清楚了之后,再来写代码就不难了 。我们先来实现这个元类:


推荐阅读