ORM概念
ORM(Object Ralational Mapping,对象关系映射)用来把对象模型表示的对象映射到基于 SQL 的关系模型数据库结构中去。这样,我们在具体的操作实体对象的时候,就不需要再去和复杂的 SQL 语句打交道,只需简单的操作实体对象的属性和方法。
一个句话理解就是:创建一个实例对象,用创建它的类名当做数据表名,用创建它的类属性对应数据表的字段,当对这个实例对象操作时,能够对应MySQL语句。
示例:
classUser(父类省略):
uid= ('uid', "int unsigned")
name= ('username', "varchar(30)")
email= ('email', "varchar(30)")
password= ('password', "varchar(30)")
...省略...
u= User(uid=12345, name='Michael', email='test@orm.org', password='my-pwd')
u.save()#对应如下sql语句#insert into User (username,email,password,uid)#values ('Michael','test@orm.org','my-pwd',12345)
__new__、__init__、__call__的介绍
在讲使用元类创建ORM之前,必须了解__new__这个内置方法的作用。
__new__方法负责创建一个实例对象,在对象被创建的时候调用该方法它是一个类方法。__new__方法在返回一个实例之后,会自动的调用__init__方法,对实例进行初始化。如果__new__方法不返回值,或者返回的不是实例,那么它就不会自动的去调用__init__方法。
__init__ 方法负责将该实例对象进行初始化,在对象被创建之后调用该方法,在__new__方法创建出一个实例后对实例属性进行初始化。__init__方法可以没有返回值。
__call__方法其实和类的创建过程和实例化没有多大关系了,定义了__call__方法才能被以函数的方式执行。
classA(object):def __call__(self):print "__call__ be called"a=A()
a()#输出:__call__ be called
通过元类简单实现ORM中的insert功能
classModelMetaclass(type):def __new__(cls, name, bases, attrs):
mappings=dict()#判断是否需要保存
for k, v inattrs.items():#判断是否是指定的StringField或者IntegerField的实例对象
ifisinstance(v, tuple):print('Found mapping: %s ==> %s' %(k, v))
mappings[k]=v#删除这些已经在字典中存储的属性
for k inmappings.keys():
attrs.pop(k)#将之前的uid/name/email/password以及对应的对象引用、类名字
attrs['__mappings__'] = mappings #保存属性和列的映射关系
attrs['__table__'] = name #假设表名和类名一致
return type.__new__(cls, name, bases, attrs)class User(metaclass=ModelMetaclass):
uid= ('uid', "int unsigned")
name= ('username', "varchar(30)")
email= ('email', "varchar(30)")
password= ('password', "varchar(30)")#当指定元类之后,以上的类属性将不在类中,而是在__mappings__属性指定的字典中存储
#以上User类中有
#__mappings__ = {
#"uid": ('uid', "int unsigned")
#"name": ('username', "varchar(30)")
#"email": ('email', "varchar(30)")
#"password": ('password', "varchar(30)")
#}
#__table__ = "User"
def __init__(self, **kwargs):for name, value inkwargs.items():
setattr(self, name, value)#设置属性值
defsave(self):
fields=[]
args=[]for k, v in self.__mappings__.items():
fields.append(v[0])
args.append(getattr(self, k, None))
args_temp=list()for temp inargs:#判断入如果是数字类型
ifisinstance(temp, int):
args_temp.append(str(temp))elifisinstance(temp, str):
args_temp.append("""'%s'""" % temp) #处理字符串类型的数据
sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join(args_temp))print('SQL: %s' %sql)
u= User(uid=12345, name='Michael', email='test@orm.org', password='my-pwd')#print(u.__dict__)
u.save()
输出结果
Found mapping: uid ==> ('uid', 'int unsigned')
Found mapping: name==> ('username', 'varchar(30)')
Found mapping: email==> ('email', 'varchar(30)')
Found mapping: password==> ('password', 'varchar(30)')
SQL: insert into User (uid,username,email,password) values (12345,'Michael','test@orm.org','my-pwd')
抽取到基类中
classModelMetaclass(type):def __new__(cls, name, bases, attrs):
mappings=dict()#判断是否需要保存
for k, v inattrs.items():#判断是否是指定的StringField或者IntegerField的实例对象
ifisinstance(v, tuple):print('Found mapping: %s ==> %s' %(k, v))
mappings[k]= v #将提取的内容存放在一个字典中
#删除这些已经在字典中存储的属性
for k inmappings.keys():
attrs.pop(k)#将之前的uid/name/email/password以及对应的对象引用、类名字
attrs['__mappings__'] = mappings #保存属性和列的映射关系
attrs['__table__'] = name #假设表名和类名一致
return type.__new__(cls, name, bases, attrs)class Model(object, metaclass=ModelMetaclass):def __init__(self, **kwargs):for name, value inkwargs.items():
setattr(self, name, value)#将name,value设置为实例属性
defsave(self):
fields=[]
args=[]for k, v in self.__mappings__.items():#fields.append(v[0]) # v为类属性的元组
fields.append(k) #如果使用v[0],那就是取元组中的uid、username等为数据库表中字段名,不利于理解,直接取类属性的变量名
args.append(getattr(self, k)) #k为类属性的变量名
args_temp=list()for temp inargs:#判断入如果是数字类型
ifisinstance(temp, int):
args_temp.append(str(temp))elifisinstance(temp, str):
args_temp.append("""'%s'""" %temp)
sql= 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join(args_temp))print('SQL: %s' %sql)class User(Model): #User对应数据库中的表名
#uid = ('uid', "int unsigned")
#username = ('name', "varchar(30)")
#email = ('email', "varchar(30)")
#password = ('password', "varchar(30)")
uid = ("int unsigned",) #数据库字操作的字段以这里的字段名为主,实例化传的变量名,主要用来匹配各个字段对应的值,可以多但不能少,否则报错
username = ("varchar(30)",)
email= ("varchar(30)",)
password= ("varchar(30)",)
u= User(uid=12345, username='Michael', email='test@orm.org', password='my-pwd', phone=12345678900)#print(u.__dict__)
u.save()
输出结果
Found mapping: uid ==> ('int unsigned',)
Found mapping: username==> ('varchar(30)',)
Found mapping: email==> ('varchar(30)',)
Found mapping: password==> ('varchar(30)',)
SQL: insert into User (uid,username,email,password) values (12345,'Michael','test@orm.org','my-pwd')
通过上面的示例,我们可以看出用元类创建API是非常好的选择,使用元类的编写虽然很复杂,但使用者可以非常简洁的调用API。