跟著廖大學python之orm框架實現(xiàn)

廢話少說

之前自己主要是做web前端的,web后臺接觸的很少,最近在學習廖大的python教程,課程最后有一個小項目,即完成一個個人博客,所以借此項目了解了解web后臺到底在做什么?而有關(guān)后臺接觸的第一個新概念就是ORM。

ORM是什么?

ORM 即Object Relational Mapping,全稱對象關(guān)系映射。
可是它到底是干啥的呢?
如果接觸過一些web后臺的化,我們知道web后臺有很大一部分工作都是數(shù)據(jù)的增刪改查,如果每次操作數(shù)據(jù)庫都要連接數(shù)據(jù)庫,構(gòu)造sql語句,執(zhí)行sql語句的話,那未免太麻煩了,所以我們可以將數(shù)據(jù)庫中的表,字段,行,與我們的面向?qū)ο缶幊讨械念悾惖膶傩?,以及對象建立一一對?yīng)的映射關(guān)系,這樣我們便可以避免直接操作數(shù)據(jù)庫,而只要調(diào)用相應(yīng)的方法就可以了。
拿我過去的做法舉個例子就明白了,比如實現(xiàn)一個用戶注冊,以前我是怎么做的,前臺拿到數(shù)據(jù),傳給后臺,然后后臺字符串拼接形成sql語句,后臺執(zhí)行。
而有了ORM以后,我只需要用數(shù)據(jù)實例化一個User對象,然后調(diào)用該對象的save方法,便保存到了數(shù)據(jù)庫,作為使用者的角度,不需要操作一句sql語句。
假設(shè)User類對應(yīng)users表

user=User(id="100001",name="Andy",password="*****")
user.save()  //保存到數(shù)據(jù)庫
user=User.findById("100001") #從數(shù)據(jù)庫中找出id為"100001"的用戶
user.update(password="*********")  #更改id為"100001"的用戶密碼
users=User.findAll() #取出users表中全部數(shù)據(jù)

我就問,這樣用起來不爽嗎?

注意

以下涉及的代碼均為本人學習廖雪峰python3教程的課程項目實踐代碼
IO操作均為異步,用到的異步庫為asyncio
鏈接的數(shù)據(jù)庫為mysql 5.7,用到的mysql異步IO驅(qū)動為aiomysql

實現(xiàn)ORM的必要準備---封裝數(shù)據(jù)庫操作

創(chuàng)建數(shù)據(jù)庫連接池

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), # 自動提交事務(wù)
    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)
    # 用參數(shù)替換而非字符串拼接可以防止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方法要返回查詢內(nèi)容,剩下的update,insert,delete均只需返回一個影響行數(shù),所以可將它們?nèi)齻€封裝為一個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

開始動手實現(xiàn)ORM

編程中有個思想叫做”自頂向下“。所以當你對如何設(shè)計ORM無從下手的時候,你可以假設(shè)已經(jīng)有一個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()

有沒有發(fā)現(xiàn),這樣看User類,很清楚,對應(yīng)user表,這個表有哪些字段,一目了然。然后讓子類繼承父類,實現(xiàn)對find,save...等方法的復(fù)用。真是完美,可是要怎么實現(xiàn)呢?

字段類的實現(xiàn)
class Field(object):
  def __init__(self,name,column_type,primary_key,default):
    self.name=name # 字段名
    self.column_type=column_type # 字段數(shù)據(jù)類型
    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 類的實現(xiàn)
# 讓Model繼承dict,主要是為了具備dict所有的功能,如get方法
# metaclass指定了Model類的元類為ModelMetaClass
class Model(dict,metaclass=ModelMetaClass):
  def __init__(self,**kw):
    super(Model,self).__init__(**kw)
  # 實現(xiàn)__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
  # 取默認值,上面字段類不是有一個默認值屬性嘛,默認值也可以是函數(shù)
  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
   # 一步異步,處處異步,所以這些方法都必須是一個協(xié)程
   #下面 self.__mappings__,self.__insert__等變量據(jù)是根據(jù)對應(yīng)表的字段不同,而動態(tài)創(chuàng)建
   @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]
元類的理解

根據(jù)上面的理解,因為數(shù)據(jù)庫中每張表的字段都不一樣,所以我們需要動態(tài)的生成類。python作為一門動態(tài)語言,可以很容易實現(xiàn)動態(tài)的創(chuàng)建類。實現(xiàn)動態(tài)創(chuàng)建類有倆種方法,一個是通過type()函數(shù),另一個是通過元類。
類是對象的模板,元類是類的模板。我們的User類繼承自Model類,而Model類的模板是元類ModelMetaClass,所以當使用者實例化一個User對象的時候,User會根據(jù)Model去創(chuàng)建,而Model則根據(jù)ModelMetaClass動態(tài)創(chuàng)建,所以user對象間接的根據(jù)ModelMetaClass創(chuàng)建。

實現(xiàn)ModelMetaClass類
class ModelMetaClass(type):
    # 元類必須實現(xiàn)__new__方法,當一個類指定通過某元類來創(chuàng)建,那么就會調(diào)用該元類的__new__方法
    # 該方法接收4個參數(shù)
    # cls為當前準備創(chuàng)建的類的對象 
    # name為類的名字,創(chuàng)建User類,則name便是User
    # bases類繼承的父類集合,創(chuàng)建User類,則base便是Model
    # attrs為類的屬性/方法集合,創(chuàng)建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同時還會拿到一些其它系統(tǒng)提供的類屬性,我們只處理自定義的類屬性,所以判斷一下
        # 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")
    # 這里的目的是去除類屬性,為什么要去除呢,因為我想知道的信息已經(jīng)記錄下來了。
    # 去除之后,就訪問不到類屬性了,如圖
image.png
    # 記錄到了mappings,fields,等變量里,而我們實例化的時候,如
    # user=User(id='10001') ,為了防止這個實例變量與類屬性沖突,所以將其去掉
    for k in mappings.keys():
        attrs.pop(k)
    # 以下都是要返回的東西了,剛剛記錄下的東西,如果不返回給這個類,又談得上什么動態(tài)創(chuàng)建呢?
    # 到此,動態(tài)創(chuàng)建便比較清晰了,各個子類根據(jù)自己的字段名不同,動態(tài)創(chuàng)建了自己
    # 下面通過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)

我的疑問

關(guān)于元類這塊的代碼,我只是理解了廖大教程的代碼,并且跟著教程自己實現(xiàn)了一遍,但是讓我自己去寫,ORM我肯定想不到用元類什么的。其實我一直有個疑問,因為我覺得僅僅通過子類繼承父類就可以實現(xiàn),為何一定要用元類呢?就是按照廖大教程的思路走下來,用元類很好,沒問題,很清晰,但是下來想一想,我覺得只需要用繼承就可實現(xiàn),。大概像下圖這樣,不知道這樣有什么大問題?

image.png

更新

針對上述問題,我在知乎上請教了python大神,回答如下:

image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,828評論 25 709
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 34,628評論 18 399
  • 我覺得人,能夠吃,真的是一件非常幸福的事情。 今天陰天,不知道怎么回事我突然想起來很久之前我吃過的一頓飯。我記得非...
    你說的黑是什么黑閱讀 310評論 1 1
  • 一念起,萬水千山。 一念滅,滄海桑田。 一念墮,萬劫不復(fù)。 一念生,百廢俱興。 一念存,亙古不滅。 一念善,四海升...
    演員Wyh閱讀 387評論 0 1
  • 今天是上班以來感覺最累的一天。也許是事情實在太多,又或許是上上級總是很莫名奇妙交代一些任務(wù),而這些任務(wù)其實并不在職...
    Anges閱讀 269評論 0 0

友情鏈接更多精彩內(nèi)容