利用python实现ORM
Posted 文大侠
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了利用python实现ORM相关的知识,希望对你有一定的参考价值。
做web开发的基本绕不过ORM,很多时候ORM可以大大减少开发工作量,那么ORM是怎样实现的呢?其实很简单,说穿了一文不值,本文先实现一个简单的ORM然后分析下现在流行的python ORM框架peewee源码。
ORM原理
ORM即对象关系映射(Object Relational Mapping,简称ORM,或O/RM,或O/R mapping),简单来说就是把数据库的一个表映射成程序中的一个对象,表中的每个字段对应程序对象的一个属性,一个表记录对应一个对象实例。
这样的好处在于,数据库的操作变得透明,底层数据库被隔离,而且操作数据的过程中感觉不到数据库的操作,始终处理的只有程序对象,程序结构性非常好。
如下图,要建立一个ORM映射包括如下:
1.类名ClassName到表名TableName的映射,通常两者相同
2.类属性先建立到对应表字段的映射,通常属性和字段名相同,表字段类型在类属性初始化时指定
ORM简单实现
按照上述,我们希望定义一个ORM类如下即可完成全部工作:
class UserModel(BaseModel):
id = Field("bigint")
name = Field("varchar(100)")
age = Field("int")
在这个类定义中,我们指定了类属性到表字段的映射关系,这里认为类属性名和表字段名相同。现在类只有映射关系,还没有具体的属性,而且映射也不是我们想要的map形式,因此程序如下:
在BaseModel中继承内置字典dict来完成类属性的设置,如下
class BaseModel(dict):
__metaclass__ = ModelMetaClass
def __init__(self, **kv):
super(BaseModel, self).__init__(**kv)
def __getattr__(self, key):
try:
return self[key]
except KeyError:
raise AttributeError("Model has not key %s" % key)
def __setattr__(self, key, value):
self[key] = value
有了类属性,需要将映射关系转成我们想要的map形式,具体映射关系在子类中指定,参考上一节,借助元类在BaseModel中我们通过指定元类hook类创建过程,如下
class Field(object):
def __init__(self, column_type):
self.column_type = column_type
class ModelMetaClass(type):
def __new__(cls, name, bases, attr_dict):
if name == 'BaseModel':
return type.__new__(cls, name, bases, attr_dict)
print("Creating Model:%s" % name)
print(attr_dict)
mapping = dict()
for k,v in attr_dict.items():
if isinstance(v, Field):
print("Found Mapping:%s=>%s" % (k, v))
setattr(v, 'name', k)
mapping[k] = v
attr_dict.pop(k)
attr_dict['__mapping__'] = mapping
attr_dict['__table__'] = name
return type.__new__(cls, name, bases, attr_dict)
在类创建时,凡是指定Field类型的类属性都是我们定义的映射关系,在此取出来填充Field属性表示字段信息,并以属性名作为key来索引对应信息,对应的表名也映射为类的名称。
到此整个映射关系建立完成,此时实现ORM就很简单了,基本上就是查表生成sql语句即可,如下
save保存一个记录,按照映射关系插入对应字段对应值即可,可在BaseModel中如下实现:
def save(self):
fields = []
params = []
args = []
for k, v in self.__mapping__.items():
fields.append(v.name)
params.append('?')
args.append(getattr(self, k, None))
sql = 'insert into %s (%s) values (%s)' %(self.__table__, ','.join(fields), ','.join(params))
print('SQL:%s' % sql)
print('ARGS:%s' % str(args))
getone获取指定id的一条记录,查询得结果,按照映射关系指定到对应属性上即可,可在BaseModel中如下实现:
@classmethod
def getone(cls, id):
# 简化,根据数据库查询生成对应数据表
data = "id":"2", "name":"wenwei", "age":"19"
a = cls()
for k, v in a.__mapping__.items():
a.__setattr__(k, data[v.name])
return a
如下调用
if __name__ == '__main__':
u1 = UserModel(id='1', name='wenzhou', age='20')
u1.save()
u2 = UserModel.getone('2')
print(u2, type(u2))
结果如下,即模拟了常见ORM的操作
Creating Model:UserModel
'age': <__main__.Field object at 0x00000000031FF710>, '__module__': '__main__', 'id': <__main__.Field object at 0x00000000031FF668>, 'name': <__main__.Field object at 0x00000000031FF6D8>
Found Mapping:age=><__main__.Field object at 0x00000000031FF710>
Found Mapping:id=><__main__.Field object at 0x00000000031FF668>
Found Mapping:name=><__main__.Field object at 0x00000000031FF6D8>
SQL:insert into UserModel (age,id,name) values (?,?,?)
ARGS:['20', '1', 'wenzhou']
('age': '19', 'id': '2', 'name': 'wenwei', <class '__main__.UserModel'>)
peewee源码分析
python下常见的ORM有django orm、SQLAlchemy和peewee,前两者太重,peewee相对来说比较轻量灵活,代码非常简洁,我们以此为例来分析下它的实现。
先看下简单的使用,如下:
from peewee import *
settings = 'host': 'localhost', 'password': '', 'port': 3306, 'user': 'root'
db = mysqlDatabase('test', **settings)
class Person(Model):
id = BigIntegerField()
name = CharField(50)
age = IntegerField()
class Meta:
database = db
db_table = 'user_test'
if __name__ == '__main__':
db.connect()
usernames = ['Bob', 'huey', 'mickey']
for person in Person.select().where(Person.id >= 5):
print(person.id, person.name, person.age)
db.close()
这里使用和我们自定义类类似,定义一个包含表字段的Model子类,然后创建连接使用即可。它这里把连接信息database和db_table当做类的元数据Meta传入。框架对数据库层操作做了封装,我们指定Person.select().where(Person.id >= 5)查询的时候,类似我们自定义操作,根据定义的字段信息拼接出对应的SQL语句执行查询,并填充对应的对象属性值。
因此我们主要看下,它的字段映射关系如何指定的,一般编辑器中Ctrl选中Model,查看定义如下:
class Model(with_metaclass(ModelBase, Node)):
def __init__(self, *args, **kwargs):
...
对应元类为ModelBase,查看定义如下:
class ModelBase(type):
...
def __new__(cls, name, bases, attrs):
...
Meta = meta_options.get('model_metadata_class', Metadata)
...
# Construct the new class.
cls = super(ModelBase, cls).__new__(cls, name, bases, attrs)
cls.__data__ = cls.__rel__ = None
cls._meta = Meta(cls, **meta_options)
cls._schema = Schema(cls, **sopts)
fields = []
for key, value in cls.__dict__.items():
if isinstance(value, Field):
if value.primary_key and pk:
raise ValueError('over-determined primary key %s.' % name)
elif value.primary_key:
pk, pk_name = value, key
else:
fields.append((key, value))
...
if pk is not False:
cls._meta.set_primary_key(pk_name, pk)
for name, field in fields:
cls._meta.add_field(name, field)
...
return cls
这里判断字段是Field实例的属性为数据库表对应字段,注意看这里add_field,展开如下:
def add_field(self, field_name, field, set_attribute=True):
if field_name in self.fields:
self.remove_field(field_name)
elif field_name in self.manytomany:
self.remove_manytomany(self.manytomany[field_name])
...
field.bind(self.model, field_name, set_attribute)
是不是很相似,先从自身移除属性,然后绑定属性名到新值,这个新值是什么,继续看bind展开:
class Field(ColumnBase):
...
accessor_class = FieldAccessor
...
def bind(self, model, name, set_attribute=True):
self.model = model
self.name = name
self.column_name = self.column_name or name
if set_attribute:
setattr(model, name, self.accessor_class(model, self, name))
class FieldAccessor(object):
def __init__(self, model, field, name):
self.model = model
self.field = field
self.name = name
def __get__(self, instance, instance_type=None):
if instance is not None:
return instance.__data__.get(self.name)
return self.field
def __set__(self, instance, value):
instance.__data__[self.name] = value
instance._dirty.add(self.name)
可以看到,这里设置的新值就是FiledAccesor,这是个属性描述符。
不妨print(Person.__dict__)如下:
'name': < peewee.FieldAccessor object at 0x000000000280B6D8 > ,
'age': < peewee.FieldAccessor object at 0x0000000002839748 > ,
'id': < peewee.FieldAccessor object at 0x0000000002843198 > ,
'_meta': < peewee.Metadata object at 0x0000000002791828 >
...
可以看到,通过元类,每个属性已经被换成属性描述符,通过这种方式,统一每个字段的行为,保证Person.name返回的是对应字段的描述,通过person.name返回的是具体对象实例的值。打印一个print(Person.name.__dict__)如下:
'column_name': 'name'
'primary_key': False,
'name': 'name',
'max_length': 50,
'unique': False,
'index': False,
'model': < Model: Person > ,
...
即为对应的数据库表信息。
可以看到peewee整个ORM实现方式和我们的如出一辙,关键是利用好属性描述符和元类来完成,前者完成每个字段的类访问和实例访问的行为统一,后者实现拦截类创建过程实现替换属性建立类-数据库表映射,把握这两点就可以分析整个ORM框架了。
演示代码下载链接
原创,转载请注明来自http://blog.csdn.net/wenzhou1219
以上是关于利用python实现ORM的主要内容,如果未能解决你的问题,请参考以下文章