僅供學(xué)習(xí)星掰,轉(zhuǎn)載請(qǐng)注明出處
元類(lèi)實(shí)現(xiàn)ORM
上一篇章大概講述了元類(lèi)的概念多望,實(shí)現(xiàn)使用元類(lèi)的方式修改一個(gè)類(lèi)的屬性大小寫(xiě)修改的功能。
那么下面可以更加深層次的使用方式氢烘,使用元類(lèi)來(lái)實(shí)現(xiàn)數(shù)據(jù)庫(kù)操作的ORM功能怀偷。
ORM是什么
ORM 是 python編程語(yǔ)言后端web框架 Django的核心思想,“Object Relational Mapping”播玖,即對(duì)象-關(guān)系映射椎工,簡(jiǎn)稱(chēng)ORM。
一個(gè)句話理解就是:創(chuàng)建一個(gè)實(shí)例對(duì)象蜀踏,用創(chuàng)建它的類(lèi)名當(dāng)做數(shù)據(jù)表名维蒙,用創(chuàng)建它的類(lèi)屬性對(duì)應(yīng)數(shù)據(jù)表的字段,當(dāng)對(duì)這個(gè)實(shí)例對(duì)象操作時(shí)果覆,能夠?qū)?yīng)MySQL語(yǔ)句颅痊。
示例代碼如下:
demo:
class User(父類(lèi)省略):
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()
# 對(duì)應(yīng)如下sql語(yǔ)句
# insert into User (username,email,password,uid)
# values ('Michael','test@orm.org','my-pwd',12345)
說(shuō)明:
- 所謂的ORM就是讓開(kāi)發(fā)者在操作數(shù)據(jù)庫(kù)的時(shí)候,能夠像操作對(duì)象時(shí)通過(guò)
xxxx.屬性=yyyy
一樣簡(jiǎn)單随静,這是開(kāi)發(fā)ORM
的初衷八千。 - 只不過(guò)
ORM
的實(shí)現(xiàn)較為復(fù)雜,Django
中已經(jīng)實(shí)現(xiàn)了 很復(fù)雜的操作燎猛,本次篇章主要通過(guò)完成一個(gè) insert相類(lèi)似的ORM恋捆,理解其中的道理就就可以了。
是呀重绷,只要能夠明白了insert的ORM基本實(shí)現(xiàn)沸停,其他的也是可以迅速寫(xiě)寫(xiě)。
要實(shí)現(xiàn)一個(gè)大功能之前昭卓,首先實(shí)現(xiàn)一個(gè)小功能愤钾,不要急。
定義一個(gè)User類(lèi)候醒,然后使用元類(lèi)可以截取打印User類(lèi)中的相關(guān)屬性
# 定義元類(lèi)
class ModelMetaclass(type):
def __new__(cls,name,bases,attrs):
print("cls=",cls)
print("name=",name)
print("bases=",bases)
print("attrs=",attrs)
return type.__new__(cls,name,bases,attrs)
# 定義User類(lèi)
class User(metaclass=ModelMetaclass):
# 定義User表的字段以及字段類(lèi)型
uid = ('uid',"int unsigned")
name = ('username',"varchar(30)")
email = ('email',"varchar(30)")
password = ('password',"varchar(30)")
def main():
u = User()
if __name__ == "__main__":
main()
運(yùn)行如下:
D:\Python37\python3.exe D:/pythonProject/test/orm.py
cls= <class '__main__.ModelMetaclass'>
name= User
bases= ()
attrs= {'__module__': '__main__', '__qualname__': 'User', 'uid': ('uid', 'int unsigned'), 'name': ('username', 'varchar(30)'), 'email': ('email', 'varchar(30)'), 'password': ('password', 'varchar(30)')}
Process finished with exit code 0
可以從運(yùn)行的結(jié)果中看出能颁,元類(lèi)能夠成功攔截打印將要?jiǎng)?chuàng)建的類(lèi)名以及該類(lèi)定義個(gè)相關(guān)屬性。
類(lèi)名:User
屬性:{'__module__': '__main__', '__qualname__': 'User', 'uid': ('uid', 'int unsigned'), 'name': ('username', 'varchar(30)'), 'email': ('email', 'varchar(30)'), 'password': ('password', 'varchar(30)')}
ORM如果需要?jiǎng)?chuàng)建表結(jié)構(gòu)的時(shí)候倒淫,就會(huì)需要相關(guān)映射關(guān)系伙菊,如下:
uid ==> ('uid', 'int unsigned')
name ==> ('username', 'varchar(30)')
email ==> ('email', 'varchar(30)')
password ==> ('password', 'varchar(30)')
那么下面來(lái)看看如何寫(xiě)一下這個(gè)映射關(guān)系。
編寫(xiě)字段映射關(guān)系
首先打印獲取一下attrs屬性字典的值來(lái)看看敌土,如下:
運(yùn)行如下:
好了镜硕,從結(jié)果來(lái)看,已經(jīng)可以獲取到User類(lèi)的屬性了返干,那么下面使用isinstance((attrs值,),tuple)
來(lái)判斷是否為元組兴枯,然后將這個(gè)映射關(guān)系打印出來(lái)看看。
運(yùn)行看看:
那么下面可以將這個(gè)元組內(nèi)容寫(xiě)入一個(gè)映射字典當(dāng)中矩欠,保存起來(lái)财剖。
好了悠夯,從上面的執(zhí)行中,我已經(jīng)將User類(lèi)的屬性中的元組保存到mappings
字典中峰伙。
然后將這個(gè)字典保存到元組屬性中疗疟,給User類(lèi)來(lái)調(diào)用,看看行不行瞳氓。
執(zhí)行看看:
從上面的結(jié)果來(lái)看策彤,元類(lèi)可以通過(guò)attrs該list來(lái)傳遞映射字典。
好了匣摘,從上面的操作來(lái)看店诗,已經(jīng)大概理解了元類(lèi)如何攔截創(chuàng)建類(lèi)的屬性,并且將修改后的結(jié)果再次傳遞給創(chuàng)建類(lèi)音榜。
那么下面庞瘸,我們?cè)賮?lái)看看,如果我需要插入一條用戶數(shù)據(jù)赠叼。大概可以執(zhí)行如下示例:
u = User(uid=12345, name='Michael', email='test@orm.org', password='my-pwd')
u.save()
編寫(xiě)通過(guò)傳遞元組給User類(lèi)擦囊,然后打印出相關(guān)元組值出來(lái)看看
好了,如果這樣的方式來(lái)創(chuàng)建類(lèi)嘴办,的確就可以接收到了一個(gè)字典瞬场。
但是如果想要構(gòu)造成為一個(gè)insert語(yǔ)句,這里的name
是需要改寫(xiě)為username
的涧郊。那么此時(shí)此刻贯被,就可以前面通過(guò)元組傳遞過(guò)來(lái)的__mappings__
映射字段來(lái)處理字段名稱(chēng)的切換。
編寫(xiě)save方法妆艘,根據(jù)__mappings__
映射來(lái)構(gòu)造insert語(yǔ)句
好了彤灶,從圖中已經(jīng)可以知道了如何設(shè)置類(lèi)的值,并且傳遞打印類(lèi)的值批旺。
下面就是根據(jù)
__mappings__
的映射關(guān)系幌陕,將對(duì)應(yīng)的name
改為username
等,然后構(gòu)建為一個(gè)insert的SQL語(yǔ)句汽煮。
看著打印出來(lái)的結(jié)果苞轿,大概可以知道該如何進(jìn)行映射修改了。
好了逗物,根據(jù)這個(gè)獲取到的值,那么就可以構(gòu)造SQL語(yǔ)句了瑟俭。
首先使用fields
的list來(lái)保存映射獲取的字段名翎卓,然后使用args
保存對(duì)應(yīng)的值。
下面就可以使用這兩個(gè)list來(lái)構(gòu)建SQL摆寄,如下:
好啦失暴,已經(jīng)成功獲取到了這個(gè)SQL語(yǔ)句了坯门。
但是看看這個(gè)SQL語(yǔ)句:
insert into User(uid,username,email,password) values(12345,Michael,test@orm.org,my-pwd)
是否發(fā)現(xiàn)一些問(wèn)題?
就是插入數(shù)據(jù)的使用逗扒,如果是int類(lèi)型古戴,的確不用加上''
引號(hào)括起來(lái),但是字符串是需要使用引號(hào)括起來(lái)的矩肩。
那么下面再來(lái)優(yōu)化一下现恼,進(jìn)行數(shù)據(jù)類(lèi)型的判斷,然后再改變拼接的方式黍檩。
好了叉袍,字符串的字段已經(jīng)增加了引號(hào)括起來(lái)了。
那么下一步再考慮一下刽酱,如果我沒(méi)寫(xiě)一個(gè)表的類(lèi)喳逛,都要寫(xiě)一個(gè)這樣的save方法,肯定很麻煩呀棵里。
那么經(jīng)過(guò)思考润文,我可以將save方法這部分再抽出來(lái)作為一個(gè)User的父類(lèi)呀,既然通用的話殿怜。
抽取通用部分為Model類(lèi)
運(yùn)行測(cè)試如下:
那么再寫(xiě)一個(gè)student表的類(lèi)來(lái)看看是否也是正车潋颍可以生成SQL語(yǔ)句。
好啦稳捆,成功生成SQL了赠法。
最后提供一下完整代碼
# 定義元類(lèi)
class ModelMetaclass(type):
def __new__(cls,name,bases,attrs):
# print("cls=",cls)
# print("name=",name)
# print("bases=",bases)
# print("attrs=",attrs)
# 定義一個(gè)映射字典
mappings = dict()
for attr in attrs:
# print("key = ",attr," ,value=",attrs[attr])
if isinstance(attrs[attr],tuple):
print("User表的元組屬性:")
print("key = ", attr, " ,value=", attrs[attr])
mappings[attr] = attrs[attr]
# print("mappings=",mappings)
#將mappings字典保存到屬性之中,進(jìn)行返回給User類(lèi)
attrs['__mappings__'] = mappings
#記錄表名
attrs['__table__'] = name
return type.__new__(cls,name,bases,attrs)
# 定義Model類(lèi)
class Model(metaclass=ModelMetaclass):
## 編寫(xiě)初始化方法乔夯,接收元組
def __init__(self, **kwargs):
print("init方法中接收的kwargs=", kwargs)
for key, value in kwargs.items():
# print("kwargs中的key=%s , value=%s" % (key,value))
## 設(shè)置屬性至該類(lèi)中
setattr(self, key, value)
def save(self):
fields = [] # 記錄字段名
args = [] # 記錄傳遞過(guò)來(lái)的參數(shù)
for key, value in self.__mappings__.items():
print("save=", key, value)
fields.append(value[0]) # 將字典的值保存到fields中
args.append(getattr(self, key, None)) # 根據(jù)key砖织,也就是例如name='Michael',則設(shè)置參數(shù)為Michael
print("save fields=%s , args=%s" % (fields, args))
# 判斷字段類(lèi)型末荐,判斷是否添加引號(hào)
args_tmp = []
for value in args:
print("args.value=", value)
if isinstance(value, int): # 如果是int類(lèi)型侧纯,則設(shè)置為str字符串,用于后續(xù)join的拼接
args_tmp.append(str(value))
if isinstance(value, str): # 如果是str類(lèi)型甲脏,則增加引號(hào)
args_tmp.append("""'%s'""" % value)
print("args_tmp=", args_tmp)
## 構(gòu)建sql insert into User(uid,username,email,password) values(12345,Michael,test@orm,org,my-pwd)
sql = "insert into %s(%s) values(%s)" % (self.__table__, ','.join(fields), ','.join(args_tmp))
print("sql=", sql)
def _test(self):
print("測(cè)試打印元組傳遞過(guò)來(lái)的mappings屬性")
print(self.__mappings__)
# 定義User類(lèi)
class User(Model):
# 定義User表的字段以及字段類(lèi)型
uid = ('uid',"int unsigned")
name = ('username',"varchar(30)")
email = ('email',"varchar(30)")
password = ('password',"varchar(30)")
# 定義Student類(lèi)
class Student(Model):
# 定義字段映射關(guān)系
uid = ('uid',"int unsigned")
name = ('username',"varchar(30)")
classroom = ('room',"varchar(30)")
def main():
u = User(uid=12345, name='Michael', email='test@orm.org', password='my-pwd')
u.save()
stu = Student(uid=123,name='fatboy',classroom='room1')
stu.save()
if __name__ == "__main__":
main()
關(guān)注微信公眾號(hào)眶熬,回復(fù)【資料】、Python块请、PHP娜氏、JAVA、web墩新,則可獲得Python贸弥、PHP、JAVA海渊、前端等視頻資料绵疲。