Python 元類(lèi)實(shí)現(xiàn) ORM

僅供學(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恋捆,理解其中的道理就就可以了。
就只是簡(jiǎn)單一個(gè)insert么

是呀重绷,只要能夠明白了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海渊、前端等視頻資料绵疲。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末哲鸳,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子盔憨,更是在濱河造成了極大的恐慌徙菠,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,430評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件郁岩,死亡現(xiàn)場(chǎng)離奇詭異婿奔,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)驯用,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門(mén)脸秽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人蝴乔,你說(shuō)我怎么就攤上這事记餐。” “怎么了薇正?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,834評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵片酝,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我挖腰,道長(zhǎng)雕沿,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,543評(píng)論 1 296
  • 正文 為了忘掉前任猴仑,我火速辦了婚禮审轮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘辽俗。我一直安慰自己疾渣,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,547評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布崖飘。 她就那樣靜靜地躺著榴捡,像睡著了一般。 火紅的嫁衣襯著肌膚如雪朱浴。 梳的紋絲不亂的頭發(fā)上吊圾,一...
    開(kāi)封第一講書(shū)人閱讀 52,196評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音翰蠢,去河邊找鬼项乒。 笑死,一個(gè)胖子當(dāng)著我的面吹牛梁沧,可吹牛的內(nèi)容都是我干的板丽。 我是一名探鬼主播,決...
    沈念sama閱讀 40,776評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼埃碱!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起酥泞,我...
    開(kāi)封第一講書(shū)人閱讀 39,671評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤砚殿,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后芝囤,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體似炎,經(jīng)...
    沈念sama閱讀 46,221評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,303評(píng)論 3 340
  • 正文 我和宋清朗相戀三年悯姊,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了羡藐。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,444評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡悯许,死狀恐怖仆嗦,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情先壕,我是刑警寧澤瘩扼,帶...
    沈念sama閱讀 36,134評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站垃僚,受9級(jí)特大地震影響集绰,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜谆棺,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,810評(píng)論 3 333
  • 文/蒙蒙 一栽燕、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧改淑,春花似錦碍岔、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,285評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至侍郭,卻和暖如春询吴,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背亮元。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,399評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工猛计, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人爆捞。 一個(gè)月前我還...
    沈念sama閱讀 48,837評(píng)論 3 376
  • 正文 我出身青樓奉瘤,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子盗温,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,455評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • 1. 簡(jiǎn)介 1.1 什么是 MyBatis 藕赞? MyBatis 是支持定制化 SQL、存儲(chǔ)過(guò)程以及高級(jí)映射的優(yōu)秀的...
    笨鳥(niǎo)慢飛閱讀 5,527評(píng)論 0 4
  • 一. Java基礎(chǔ)部分.................................................
    wy_sure閱讀 3,813評(píng)論 0 11
  • 轉(zhuǎn)載卖局,覺(jué)得這篇寫(xiě) SQLAlchemy Core斧蜕,寫(xiě)得非常不錯(cuò)。不過(guò)后續(xù)他沒(méi)寫(xiě)SQLAlchemy ORM... ...
    非夢(mèng)nj閱讀 5,420評(píng)論 1 14
  • 咨詢(xún)本身就是一個(gè)間接推銷(xiāo)過(guò)程砚偶,通過(guò)透視求美者內(nèi)心的想法批销,采用不讓其反感的方法進(jìn)行營(yíng)銷(xiāo)。在整個(gè)咨詢(xún)過(guò)程中染坯,整形咨詢(xún)師...
    莎莎微整形閱讀 274評(píng)論 0 0
  • 青蜂尋 百花爭(zhēng) 蜜于身便甜可心 花蝶舞 草瑩空 亂似纖輕醉舊人 蠶絲綢緞棉似柳 落葉枯靈憶知秋 乾坤擲 陰陽(yáng)咒 死...
    伊誠(chéng)ES閱讀 283評(píng)論 2 5