ORM
全称“Object Relational Mapping”,即对象-关系映射,就是把关系数据库的一行映射为一个对象,也就是一个类对应一个表,这样,写代码更简单,不用直接操作SQL
语句。
要编写一个ORM
框架,所有的类都只能动态定义,因为只有使用者才能根据表的结构定义出对应的类来。在Python
中类的动态定义可以由元类来实现。
元类
type
type
函数不仅可以查看一个类型或变量的类型,type
也能动态的创建类。type
可以接受一个类的描述作为参数,然后返回一个类。
type
可以像这样工作:
type(类名, 父类的元组(针对继承的情况,可以为空),包含属性的字典(名称和值))
比如下面的代码:
1 | class Hello(object): |
可以手动像这样创建:
1 | def fn(self, name='world'): |
可以看到使用type
函数创建的类和直接写class是完全一样的,因为type
就是Python
在背后用来创建所有类的元类。Python
解释器在遇到class定义时,仅仅是扫描一下class定义的语法,然后调用type()
函数创建出class。
metaclass
除了使用type()
动态创建类以外,要控制类的创建行为,还可以使用metaclass
。metaclass
允许你创建类或者修改类。换句话说,你可以把类看成是metaclass
创建出来的“实例”。
下面的代码使用元类将模块里所有的类的属性都修改为大写形式:
1 |
|
有了UpperAttrMetaClass
,在定义类的时候还要指示使用UpperAttrMetaClass
来定制类,传入关键字参数metaclass
:
1 | class Foo(object, metaclass=UpperAttrMetaClass): |
当传入关键字参数metaclass
时,它指示Python
解释器在创建Foo
类时,要通过ListMetaclass.__new__()
来创建,在此,将类的属性都修改为大写形式然后,返回修改后的定义。
__new__()
方法接收到的参数依次是:
当前准备创建的类的对象;
类的名字;
类继承的父类集合;
类的属性集合。
1 | 'bar')) print(hasattr(Foo, |
ORM
设计ORM
需要从上层调用者角度来设计,如:定义一个User
类来操作对应的数据库表User
,我们期待他写出这样的代码:
1 | class User(Model): |
在User
类中的__table__
、id
和name
是类的属性,不是实例的属性。所以,在类级别上定义的属性用来描述User
对象和表的映射关系,而实例属性必须通过__init__()
方法去初始化,所以两者互不干扰.
定义Field类
Field
类,它负责保存数据库表的字段名和字段类型:
1 | class Field(object): |
StringField、FloatField
映射varchar
的StringField
,映射rear
的FloatField
:
1 | class StringField(Field): |
在Field
的基础上,进一步定义各种类型的Field
,比如IntegerField
等等。
ModelMetaclass
ModelMetaclass
用于保存子类如User
的映射信息:
1 | def create_args_string(num): |
在ModelMetaclass
中,一共做了几件事情:
排除掉对
Model
类的修改把表名保存到
__table__
中,如果当前类没有定义__table__
属性,则使用类名作为表名在当前类(比如
User
)中查找定义的类的所有属性,如果找到一个Field
属性,就把它保存到一个__mappings__
的dict
中,防止主键属性重复;从类属性中删除该
Field
属性,否则,容易造成运行时错误(实例的属性会遮盖类的同名属性)__select__
、__insert__
、__update__
等,对应于数据表的SQL
操作
定义Model
当用户定义一个class User(Model)
时,Python
解释器首先在当前类User
的定义中查找metaclass
,如果没有找到,就继续在父类Model
中查找metaclass
,找到了,就使用Model
中定义的metaclass
的ModelMetaclass
来创建User
类,也就是说,metaclass
可以隐式地继承到子类。
1 | class Model(dict, metaclass=ModelMetaClass): |
Model
从dict
继承,所以具备所有dict
的功能,同时又实现了特殊方法__getattr__()
和__setattr__()
,因此又可以像引用普通字段那样写:
>>> user['id']
123
>>> user.id
123
Model
类定义了一个save
的实例方法,可以把一个User
实例存入数据库,save
方法使用的异步IO来操作数据库:
1 | user = User(email='test@test.com', passwd='123456', name='test') |
get_value_or_default
函数获得对应属性的值,如果对应属性的值为None
,则取__mappings__
里的默认值,如果default
属性是callable
,则取出调用后的值。如User
类的id
属性为default=next_id
。
创建连接池
上面的save
方法使用的是异步IO来操作数据库,在Python
中,aiomysql
为MySQL
数据库提供了异步IO的驱动。
下面的代码创建了一个连接池,由全局变量__pool
存储:
1 | async def create_pool(loop, **kw): |
执行INSERT语句
上面的Model
类的save
的实例方法,可以把一个实例对象保存到数据库中,由excute
函数执行insert
语句来完成。
1 |
|
编写Model
创建一个users
表对应的Model
:
1 | import time |
初始化数据库表
1 | drop database if exists test; |
把SQL
脚本放到MySQL
命令行里执行:
mysql -u root -p < schema.sql
可以在数据库中看到,创建了一个test
数据库,并创建类一个users
表,users
表中字段对应于User
类的各个属性:
1 | mysql> USE test; |
编写数据访问代码
接下来,就可以真正开始编写代码操作对象了。比如,对于User
对象,我们就可以做如下操作:
1 | import asyncio |
执行成功后,可以到如下log
:
INFO:root:found model: User (table: users)
INFO:root: found mapping: passwd ==> <StringField, varchar(50):None>
INFO:root: found mapping: id ==> <StringField, varchar(50):None>
INFO:root: found mapping: email ==> <StringField, varchar(50):None>
INFO:root: found mapping: name ==> <StringField, varchar(50):None>
INFO:root: found mapping: created_at ==> <FloatField, real:None>
INFO:root:create database connection pool...
INFO:root:SQL: insert into `users` (`passwd`, `email`, `name`, `created_at`, `id`) values (?, ?, ?, ?, ?)
INFO:root:SQL: insert into `users` (`passwd`, `email`, `name`, `created_at`, `id`) values (?, ?, ?, ?, ?)
INFO:root:SQL: insert into `users` (`passwd`, `email`, `name`, `created_at`, `id`) values (?, ?, ?, ?, ?)
INFO:root:SQL: insert into `users` (`passwd`, `email`, `name`, `created_at`, `id`) values (?, ?, ?, ?, ?)
查看数据库,可以看到数据插入成功:
1 | mysql> SELECT * FROM users; |
SELECT语句
根据主键查找
在Model
类中定义一个find
的类方法,find
函数根据传入的主键执行select
函数,返回相应的记录。
1 | class Model(dict, metaclass=ModelMetaClass): |
select函数
select
函数从数据库查找相应的记录:
1 | async def select(sql, args, size=None): |
执行下面find
方法的调用:
1 | async def test(loop): |
输出如下:
INFO:root:create database connection pool...
INFO:root:SQL: select `id`, `name`, `created_at`, `email`, `passwd` from `users` where `id`=?
INFO:root:rows returned: 1
{'created_at': 1527820029.53404, 'id': '001527820029534e0c48803c8cc4c44a278d1491190a069000', 'email': 'test1@test.com', 'passwd': '123456', 'name': 'test1'}
根据where、order by、limit查找
1 | class Model(dict, metaclass=ModelMetaClass): |
默认查找数据表中的所有记录,如下代码:
1 | async def test(loop): |
输出如下结果:
INFO:root:create database connection pool...
INFO:root:SQL: select `id`, `email`, `passwd`, `created_at`, `name` from `users`
INFO:root:rows returned: 4
[{'id': '001527820029534e0c48803c8cc4c44a278d1491190a069000', 'email': 'test1@test.com', 'passwd': '123456', 'created_at': 1527820029.53404, 'name': 'test1'}, {'id': '0015278200295492bdd1c9ba409444eb35211844501a663000', 'email': 'test2@test.com', 'passwd': '123456', 'created_at': 1527820029.54944, 'name': 'test2'}, {'id': '0015278200295619ab4469834ec4b3290a460d079c91f3b000', 'email': 'test3@test.com', 'passwd': '123456', 'created_at': 1527820029.56111, 'name': 'test3'}, {'id': '0015278200295635ed7e1100f294efb8b6ef8f57dbd7188000', 'email': 'test4@test.com', 'passwd': '123456', 'created_at': 1527820029.56394, 'name': 'test2'}]
根据where条件查找,如下代码:
1 | async def test(loop): |
输出如下结果:
INFO:root:create database connection pool...
INFO:root:SQL: select `id`, `created_at`, `email`, `passwd`, `name` from `users` where name=?
INFO:root:rows returned: 2
[{'created_at': 1527820029.54944, 'name': 'test2', 'email': 'test2@test.com', 'passwd': '123456', 'id': '0015278200295492bdd1c9ba409444eb35211844501a663000'}, {'created_at': 1527820029.56394, 'name': 'test2', 'email': 'test4@test.com', 'passwd': '123456', 'id': '0015278200295635ed7e1100f294efb8b6ef8f57dbd7188000'}]
使用limit,如下代码:
1 | async def test(loop): |
输出如下结果:
INFO:root:create database connection pool...
INFO:root:SQL: select `id`, `passwd`, `email`, `created_at`, `name` from `users` limit ?, ?
INFO:root:rows returned: 2
[{'passwd': '123456', 'email': 'test2@test.com', 'name': 'test2', 'created_at': 1527820029.54944, 'id': '0015278200295492bdd1c9ba409444eb35211844501a663000'}, {'passwd': '123456', 'email': 'test3@test.com', 'name': 'test3', 'created_at': 1527820029.56111, 'id': '0015278200295619ab4469834ec4b3290a460d079c91f3b000'}]
使用where、limit查找,如下代码:
1 | async def test(loop): |
输出如下结果:
INFO:root:create database connection pool...
INFO:root:SQL: select `id`, `passwd`, `created_at`, `name`, `email` from `users` where name=? limit ?
INFO:root:rows returned: 1
[{'name': 'test2', 'email': 'test2@test.com', 'id': '0015278200295492bdd1c9ba409444eb35211844501a663000', 'passwd': '123456', 'created_at': 1527820029.54944}]
使用order by,如下代码:
1 | async def test(loop): |
输出如下结果:
INFO:root:create database connection pool...
INFO:root:SQL: select `id`, `created_at`, `name`, `email`, `passwd` from `users` order by name
INFO:root:rows returned: 4
[{'passwd': '123456', 'created_at': 1527820029.53404, 'id': '001527820029534e0c48803c8cc4c44a278d1491190a069000', 'name': 'test1', 'email': 'test1@test.com'}, {'passwd': '123456', 'created_at': 1527820029.54944, 'id': '0015278200295492bdd1c9ba409444eb35211844501a663000', 'name': 'test2', 'email': 'test2@test.com'}, {'passwd': '123456', 'created_at': 1527820029.56394, 'id': '0015278200295635ed7e1100f294efb8b6ef8f57dbd7188000', 'name': 'test2', 'email': 'test4@test.com'}, {'passwd': '123456', 'created_at': 1527820029.56111, 'id': '0015278200295619ab4469834ec4b3290a460d079c91f3b000', 'name': 'test3', 'email': 'test3@test.com'}]
其他SQL语句
上面实现了数据库的insert
、select
操作,还可以自行定义其他SQL
的执行操作,如uptate
、delete
,代码实现如下:
1 | class Model(dict, metaclass=ModelMetaClass): |