Python实现ORM的一些理解

这里给出廖雪峰编写的orm例子的一些注释。

# -*- coding:utf-8 -*-

import asyncio,logging
import aiomysql


def log(sql,args=()):
	logging.info('SQL:%s'%sql)

async def create_pool(loop,**kw):
	logging.info('create database connection pool...')
	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','utf-8'),
		autocommit=kw.get('autocommit',True),
		maxsize=kw.get('maxsize',10),
		minsize=kw.get('minsize',1),
		loop=loop
	)
	
	
async def select(sql,args,size=None):
	log(sql,args)
	global __pool
	#建立一个游标,await将调用一个子协程,返回结果。
	async with __pool.get() as conn:
		async with conn.cursor(aiomysql.DictCursor) as cur:
			await cur.execute(sql.replace('?','%s'),args or ()) #SQL的占位符为?,MySQL的占位符为%s
			if size:
				rs=await cur.fetchmany(size)
			else:
				rs=await cur.fetchall()
		logging.info('rows returned:%s'% len(rs))
		return rs

#insert,update,delete操作的参数一样,
#故将其一起封装,返回影响的行号
async def execute(sql,args,autocommit=True):
	log(sql)
	async with __pool.get() as conn:
		if not autocommit:
			await conn.begin()  #开启事物
		try:
			async with conn.cursor(aiomysql.DictCursor) as cur:
				await cur.execute(sql.replace('?','%s'),args)
				affected=cur.rowcount     #返回的结果,为影响的行数
			if not autocommit:
				await conn.commit()   #事物提交
		except BaseException as e:
			if not autocommit:
				await conn.rollback() #事物回滚
			raise
		return affected     
		
#将查询的字段计数替换成MySQL可以识别的?
def create_args_string(num):
	L=[]
	for n in range(num):
		L.append('?')
	return ', '.join(L)
	
#Field类负责保存表的字段名和字段类型
class Field(object):
	'''
	给Field增加一个default参数可以让ORM自己填入缺省值;缺省值也可作为函数对象
	传入,在调用save()时自动计算
	'''
	#四个参数分别为:
	#字段名
	#字段类型
	#是否主键
	#默认值
	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:%s>' %(self.__class__.__name__,self.column_type,self.name)
		
class StringField(Field):    #映射varchar
	
	def __init__(self,name=None,primary_key=False,default=None,ddl='varchar(100)'):
		super().__init__(name,ddl,primary_key,default)
	
#布尔类型不可为主键	
class BooleanField(Field):   #映射boolean
	
	def __init__(self,name=None,default=False):
		super().__init__(name,'boolean',False,default)
		
class IntegerField(Field):   #映射bigint

	def __init__(self,name=None,primary_key=False,default=0):
		super().__init__(name,'bigint',primary_key,default)
		
class FloatField(Field):     #映射real

	def __init__(self,name=None,primary_key=False,default=0.0):
		super().__init__(name,'real',primary_key,default)
		
class TextField(Field):      #映射text

	def __init__(self,name=None,default=None):
		super().__init__(name,'text',False,default)
		
	
#定义Model的元类
#所有的元类都是继承自type
#ModelMetaclass元类定义了所有的Model类的子类实现的操作。  

#ModelMetaclass主要是将一个数据库表映射成一个封装的类做准备
#读取具体子类(user)的映射信息
#创造类的时候,排除对Model类的修改
#在当前类中查找所有的类属性(attrs),若找到Field属性,将其保存至__mappings__中,并从类属性中删除Field(防止实例属性遮住类的同名属性)
#将数据库表名保存到__table__中  

#随后便可在Model中定义各种数据库的操作方法
#metaclass是类的模板,必须从type类派生
class ModelMetaclass(type):
	
	#四个参数分别为:
	#cls:代表要__init__的类,实例化时由python解释器自动提供
	#name:当前调用的类的名字,如Model,User
	#bases:代表继承父类的集合
	#attrs:类的方法的集合
	def __new__(cls,name,bases,attrs):
		#若类为Model,则不进行修改
		if name=='Model':
			return type.__new__(cls,name,bases,attrs)
		#获取table的名称
		tableName=attrs.get('__table__',None) or name
		logging.info('found model: %s (table:%s)' %(name,tableName))
		#获取所有的主键名和Field
		mappings=dict()
		fields=[]
		primaryKey=None
		#k表示字段名
		for k,v in attrs.items():
			if isinstance(v,Field):    #v为各种Field类
				logging.info(' found mapping: %s ==> %s'% (k,v))
				print(' found mapping: %s ==> %s'% (k,v))
				mappings[k]=v
				if v.primary_key:
					#只能有一个主键,即找到一个主键后primaryKey赋值,若再出现主键则引发错误
					if primaryKey:   
						raise StandardError('Duplicate primary key for field:%s'% k)
					primaryKey=k 
				else:
					fields.append(k)  #除了主键
		
		print(fields)
		print(mappings.get('name').name)
		print(type(attrs))
		print(primaryKey)
		print(mappings)
		
		if not primaryKey:
			raise StandardError('Primary key not found.')
		#从类属性中删除Field属性
		for k in mappings.keys():
			attrs.pop(k)
		#保存除主键外的属性
		escaped_fields=list(map(lambda f:'`%s`' %f, fields))   #map返回一个iterator,再转换成一个list
		print(escaped_fields)
		attrs['__mappings__']=mappings #保存属性和列的映射关系
		attrs['__table__']=tableName
		attrs['__primary_key__']=primaryKey #主键属性名
		attrs['__fields__']=fields #除主键外的属性名
		
		#构造增删改查语句
		#select `id`,`name` from `users`
		attrs['__select__']='select `%s`,%s from `%s`' %(primaryKey,', '.join(escaped_fields),tableName)
		
		#insert into `users` (`name`,`id`) values (?, ?)
		attrs['__insert__']='insert into `%s` (%s,`%s`) values (%s)' %(tableName,', '.join(escaped_fields),primaryKey,create_args_string(len(escaped_fields) + 1))
		
		#update `users` set `name`=? where `id`=?
		attrs['__update__']='update `%s` set %s where `%s`=?' %(tableName,', '.join(map(lambda f: '`%s`=?' % (mappings.get(f).name or f), fields)), primaryKey)
		
		#delete from `users` where `id`=?
		attrs['__delete__']='delete from `%s` where `%s`=?' %(tableName,primaryKey)
		return type.__new__(cls,name,bases,attrs)
		

#定义ORM所有映射的基类:Model
#Model类的子类都可以映射一个数据库表
#Model类可看作对所有数据库表操作的基本定义的映射

#Model继承dict,实现了__getattr__和__setattr__,可实现属性操作
#实现数据库操作的所有方法,定义class方法,所有继承自Model都具有数据库操作方法。
class Model(dict,metaclass=ModelMetaclass):
	
	def __init__(self,**kw):
		super(Model,self).__init__(**kw)
		
	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):
		return getattr(self,key,None)
		
	def getValueOrDefault(self,key):     #获得value或者默认值
		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  #若field.default为可调用对象,则通过field.default()调用,直接field.default不可以吗?
				logging.debug('using default value for %s: %s' %(key,str(value)))
				setattr(self,key,value)
		return value
		
	@classmethod
	async def findAll(cls,where=None,args=None,**kw):  #根据where条件查找,  kw???
		'find object by where clause.'
		print(cls)
		sql=[cls.__select__]
		if where:
			sql.append('where')
			sql.append(where)
		if args is None:
			args=[]
		orderBy=kw.get('orderBy',None)
		if orderBy:
			sql.append('order by')
			sql.append(orderBy)
		limit=kw.get('limit',None)
		if limit is not None:
			sql.append('limit')
			if isinstance(limit,int):
				sql.append('?')
				args.append(limit)
			elif isinstance(limit,tuple) and len(limit)==2:
				sql.append('?','?')
				args.extend(limit)
			else:
				raise ValueError('Invalid limit value:%s' % str(limit))
		rs=await select(' '.join(sql),args)
		return [cls(**r) for r in rs]
		
	
	@classmethod
	async def findNumber(cls,selectField,where=None,args=None): #根据where条件查找,返回整数,适用select count(*)的SQL
		'find number by select and where'
		sql=['select %s _num_ from `%s`' % (selectField,cls.__table__)]
		if where:
			sql.append('where')
			sql.append(where)
		rs=await select(' '.join(sql),args,1)
		if len(rs) ==0:
			return None
		return rs[0]['_num_']          
		
	@classmethod
	async def find(cls,pk):
		'find object by primary key.'
		rs=await select('%s where `%s`=?' %(cls.__select__,cls.__primary_key__),[pk],1)   #返回结果
		if len(rs)==0:
			return None
		return cls(**rs[0])        
		
	async def save(self):
		args=list(map(self.getValueOrDefault,self.__fields__))   #将__fields__作为getValueOrDefault的参数   ?????为什么save时调用这个
		args.append(self.getValueOrDefault(self.__primary_key__))  #获得主键的值
		rows=await execute(self.__insert__,args)                  #执行插入命令
		
	async def update(self):
		args=list(map(self.getValue,self.__fields__))    #将__fields__作为getValue的参数,即获取除主键外的属性值
		args.append(self.getValue(self.__primary_key__)) #获得主键的值
		rows=await execute(self.__update__,args)         #执行更新命令
		if rows!=1:
			logging.warn('failed to update primary key: affected rows:%s'%rows)
			
	async def remove(self):
		args=[self.getValue(self.__primary_key__)]   #获得主键的值
		rows=await execute(self.__delete__,args)     #执行删除命令
		if rows!=1:
			logging.warn('failed to remove by primary key:affected rows:%s'%rows)

class User(Model):
	__table__='users'
	
	id=IntegerField(primary_key=True)
	name=StringField()   

一些理解

1、关于autocommit

autocommit是MySQL中Innodb数据表中特有的,它会在做出修改后立即提交至数据库。有关介绍可以看我这篇MySQL中的autocommit

2、list(map(lambda f:’%s’ %f, fields))的理解

map(function, iterable, …)

Return an iterator that applies function to every item of iterable, yielding the results. If additional iterable arguments are passed, function must take that many arguments and is applied to the items from all iterables in parallel. With multiple iterables, the iterator stops when the shortest iterable is exhausted. For cases where the function inputs are already arranged into argument tuples, see itertools.starmap().

这句话是将fields元素都依次作为lambda表示的匿名函数的参数,map()函数将返回一个iterator,而后list()将其变为一个list。

3、关于协程

如今的用法为async以代替@asyncio.coroutine,并且使用await代替yield from。更多关于协程的知识可以参考Python异步IO:asyncio模块

4、关于metaclass

上一篇文章中我们说到,使用元类就是在创建类的时候修改类的特性,然后将该类返回作为新建的类。

在这个例子中,我们对数据库的字段进行映射,在创建User类时调用ModelMetaclass类对User的属性进行添加删除等形为,形成符合数据库操作的属性,如除主键外的属性名__fields__、主键属性名__primary_key__及保存增删改查语句的属性等;然后将该类返回。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值