跟着廖大学python之orm框架实现(内附python教程分享)
废话少说
之前自己主要是做web前端的,web后台接触的很少,最近在学习廖大的python教程,课程最后有一个小项目,即完成一个个人博客,所以借此项目了解了解web后台到底在做什么?而有关后台接触的第一个新概念就是ORM。
ORM是什么?
ORM 即Object Relational Mapping,全称对象关系映射。
可是它到底是干啥的呢?
如果接触过一些web后台的化,我们知道web后台有很大一部分工作都是数据的增删改查,如果每次操作数据库都要连接数据库,构造sql语句,执行sql语句的话,那未免太麻烦了,所以我们可以将数据库中的表,字段,行,与我们的面向对象编程中的类,类的属性,以及对象建立一一对应的映射关系,这样我们便可以避免直接操作数据库,而只要调用相应的方法就可以了。
拿我过去的做法举个例子就明白了,比如实现一个用户注册,以前我是怎么做的,前台拿到数据,传给后台,然后后台字符串拼接形成sql语句,后台执行。
而有了ORM以后,我只需要用数据实例化一个User对象,然后调用该对象的save方法,便保存到了数据库,作为使用者的角度,不需要操作一句sql语句。
假设User类对应users表
user=User(id="100001",name="Andy",password="*****") user.save() //保存到数据库 user=User.findById("100001") #从数据库中找出id为"100001"的用户 user.update(password="*********") #更改id为"100001"的用户密码 users=User.findAll() #取出users表中全部数据
我就问,这样用起来不爽吗?
注意
以下涉及的代码均为本人学习廖雪峰python3教程的课程项目实践代码
IO操作均为异步,用到的异步库为asyncio
链接的数据库为mysql 5.7,用到的mysql异步IO驱动为aiomysql
实现ORM的必要准备---封装数据库操作
创建数据库连接池
import asyncio import aiomysql async def create_pool(**kw): global __pool __pool=await aiomysql.create_pool( host=kw.get('host','localhost'), port=kw.get('port',3306), user=kw['user'], password=kw['password'], db=kw['db'], charset=kw.get('charset','utf8'), autocommit=kw.get('autocommit',True), # 自动提交事务 maxsize=kw.get('maxsize',10), # 池中最多有10个链接对象 minsize=kw.get('minsize',1), )
封装select方法
async def select(sql,args,size=None): //size可以决定取几条 global __pool with (await __pool) as conn: cur=await conn.cursor(aiomysql.DictCursor) # 用参数替换而非字符串拼接可以防止sql注入 await cur.execute(sql.replace('?','%s'),args) if size: rs=await cur.fetchmany(size) else: rs=await cur.fetchall() await cur.close() return rs
除了select方法要返回查询内容,剩下的update,insert,delete均只需返回一个影响行数,所以可将它们三个封装为一个execute方法
封装execute方法(update,insert,delete)
def execute(sql,args): global __pool try: with (await __pool) as conn: cur=await conn.cursor() await cur.execute(sql.replace('?', '%s'), args) affected=cur.rowcount await cur.close() except BaseException as e: raise e return affected
开始动手实现ORM
编程中有个思想叫做”自顶向下“。所以当你对如何设计ORM无从下手的时候,你可以假设已经有一个ORM框架,你想怎么用?
class Model(object): async def find(self): pass class User(Model): # 注意这里的都是类属性 __table__="users" id=StringField(...) name=StringField(...) user=User(id="10001",name="Andy") user.save()
有没有发现,这样看User类,很清楚,对应user表,这个表有哪些字段,一目了然。然后让子类继承父类,实现对find,save...等方法的复用。真是完美,可是要怎么实现呢?
字段类的实现
class Field(object): def __init__(self,name,column_type,primary_key,default): self.name=name # 字段名 self.column_type=column_type # 字段数据类型 self.primary_key=primary_key # 是否是主键 self.default=default # 有无默认值 def __str__(self): return '<%s:%s>' % (self.__class__.__name__,self.name) class StringField(Field): def __init__(self,name=None,primary_key=False,default=None,ddl='varchar(100)'): super(StringField,self).__init__(name,ddl,primary_key,default) # 其它字段略,一个道理,一个模式
Model 类的实现
# 让Model继承dict,主要是为了具备dict所有的功能,如get方法 # metaclass指定了Model类的元类为ModelMetaClass class Model(dict,metaclass=ModelMetaClass): def __init__(self,**kw): super(Model,self).__init__(**kw) # 实现__getattr__与__setattr__方法,可以使引用属性像引用普通字段一样 如self['id'] def __getattr__(self,key): try: return self[key] except KeyError: raise AttributeError(r"'Model' object has no attribute '%s'" % key) def __setattr__(self,key,value): self[key]=value # 貌似有点多次一举 def getValue(self,key): value=getattr(self,key,None) return value # 取默认值,上面字段类不是有一个默认值属性嘛,默认值也可以是函数 def getValueOrDefault(self,key): value=getattr(self,key,None) if value is None: field=self.__mappings__[key] if field.default is not None: value=field.default() if callable(field.default) else field.default setattr(self,key,value) return value # 一步异步,处处异步,所以这些方法都必须是一个协程 #下面 self.__mappings__,self.__insert__等变量据是根据对应表的字段不同,而动态创建 @asyncio.coroutine def save(self): args=list(map(self.getValueOrDefault,self.__mappings__)) yield from execute(self.__insert__,args) @asyncio.coroutine def remove(self): args=[] args.append(self[self.__primaryKey__]) print(self.__delete__) yield from execute(self.__delete__,args) @asyncio.coroutine def update(self,**kw): print("enter update") args=[] for key in kw: if key not in self.__fields__: raise RuntimeError("field not found") for key in self.__fields__: if key in kw: args.append(kw[key]) else: args.append(getattr(self,key,None)) args.append(getattr(self,self.__primaryKey__)) yield from execute(self.__update__,args) # 类方法 @classmethod @asyncio.coroutine def find(cls,pk): rs = yield from select('%s where `%s`=?' % (cls.__select__, cls.__primaryKey__), [pk], 1) if len(rs) == 0: return None return cls(**rs[0]) # 返回的是一个实例对象引用 @classmethod @asyncio.coroutine def findAll(cls,where=None,args=None): sql=[cls.__select__] if where: sql.append('where') sql.append(where) if args is None: args=[] rs=yield from select(' '.join(sql),args) return [cls(**r) for r in rs]
元类的理解
根据上面的理解,因为数据库中每张表的字段都不一样,所以我们需要动态的生成类。python作为一门动态语言,可以很容易实现动态的创建类。实现动态创建类有俩种方法,一个是通过type()函数,另一个是通过元类。
类是对象的模板,元类是类的模板。我们的User类继承自Model类,而Model类的模板是元类ModelMetaClass,所以当使用者实例化一个User对象的时候,User会根据Model去创建,而Model则根据ModelMetaClass动态创建,所以user对象间接的根据ModelMetaClass创建。
实现ModelMetaClass类
class ModelMetaClass(type): # 元类必须实现__new__方法,当一个类指定通过某元类来创建,那么就会调用该元类的__new__方法 # 该方法接收4个参数 # cls为当前准备创建的类的对象 # name为类的名字,创建User类,则name便是User # bases类继承的父类集合,创建User类,则base便是Model # attrs为类的属性/方法集合,创建User类,则attrs便是一个包含User类属性的dict def __new__(cls,name,bases,attrs): # 因为Model类是基类,所以排除掉,如果你print(name)的话,会依次打印出Model,User,Blog,即 # 所有的Model子类,因为这些子类通过Model间接继承元类 if name=="Model": return type.__new__(cls,name,bases,attrs) # 取出表名,默认与类的名字相同 tableName=attrs.get('__table__',None) or name logging.info('found model: %s (table: %s)' % (name, tableName)) # 用于存储所有的字段,以及字段值 mappings=dict() # 仅用来存储非主键意外的其它字段,而且只存key fields=[] # 仅保存主键的key primaryKey=None # 注意这里attrs的key是字段名,value是字段实例,不是字段的具体值 # 比如User类的id=StringField(...) 这个value就是这个StringField的一个实例,而不是实例化 # 的时候传进去的具体id值 for k,v in attrs.items(): # attrs同时还会拿到一些其它系统提供的类属性,我们只处理自定义的类属性,所以判断一下 # isinstance 方法用于判断v是否是一个Field if isinstance(v,Field): mappings[k]=v if v.primary_key: if primaryKey: raise RuntimeError("Douplicate primary key for field :%s" % key) primaryKey=k else: fields.append(k) # 保证了必须有一个主键 if not primaryKey: raise RuntimeError("Primary key not found") # 这里的目的是去除类属性,为什么要去除呢,因为我想知道的信息已经记录下来了。 # 去除之后,就访问不到类属性了,如图
image.png
# 记录到了mappings,fields,等变量里,而我们实例化的时候,如 # user=User(id='10001') ,为了防止这个实例变量与类属性冲突,所以将其去掉 for k in mappings.keys(): attrs.pop(k) # 以下都是要返回的东西了,刚刚记录下的东西,如果不返回给这个类,又谈得上什么动态创建呢? # 到此,动态创建便比较清晰了,各个子类根据自己的字段名不同,动态创建了自己 # 下面通过attrs返回的东西,在子类里都能通过实例拿到,如self attrs['__mappings__']=mappings attrs['__table__']=tableName attrs['__primaryKey__']=primaryKey attrs['__fields__']=fields # 只是为了Model编写方便,放在元类里和放在Model里都可以 attrs['__select__']="select %s ,%s from %s " % (primaryKey,','.join(map(lambda f: '%s' % (mappings.get(f).name or f ),fields )),tableName) attrs['__update__']="update %s set %s where %s=?" % (tableName,', '.join(map(lambda f: '`%s`=?' % (mappings.get(f).name or f), fields)),primaryKey) attrs['__insert__']="insert into %s (%s,%s) values (%s);" % (tableName,primaryKey,','.join(map(lambda f: '%s' % (mappings.get(f).name or f),fields)),create_args_string(len(fields)+1)) attrs['__delete__']="delete from %s where %s= ? ;" % (tableName,primaryKey) return type.__new__(cls,name,bases,attrs)
我的疑问
关于元类这块的代码,我只是理解了廖大教程的代码,并且跟着教程自己实现了一遍,但是让我自己去写,ORM我肯定想不到用元类什么的。其实我一直有个疑问,因为我觉得仅仅通过子类继承父类就可以实现,为何一定要用元类呢?就是按照廖大教程的思路走下来,用元类很好,没问题,很清晰,但是下来想一想,我觉得只需要用继承就可实现,。大概像下图这样,不知道这样有什么大问题?
最后,想学习Python的小伙伴们!
请关注+私信回复:“学习”就可以拿到一份我为大家准备的Python学习资料!
pytyhon学习资料
python学习资料