ORM 手寫簡易版的對象關係映射 ...
準備知識
DBUtils模塊 <<-----重點
DBUtils是Python的一個用於實現資料庫連接池的模塊
此連接池有兩種連接模式:
DBUtils提供兩種外部介面:
PersistentDB :提供線程專用的資料庫連接,並自動管理連接。
PooledDB :提供線程間可共用的資料庫連接,並自動管理連接。
from DBUtils.PooledDB import PooledDB import pymysql POOL = PooledDB( creator=pymysql, # 使用鏈接資料庫的模塊 maxconnections=6, # 連接池允許的最大連接數,0和None表示不限制連接數 mincached=2, # 初始化時,鏈接池中至少創建的空閑的鏈接,0表示不創建 maxcached=5, # 鏈接池中最多閑置的鏈接,0和None不限制 maxshared=3, # 鏈接池中最多共用的鏈接數量,0和None表示全部共用。PS: 無用,因為pymysql和MySQLdb等模塊的 threadsafety都為1,所有值無論設置為多少,_maxcached永遠為0,所以永遠是所有鏈接都共用。 blocking=True, # 連接池中如果沒有可用連接後,是否阻塞等待。True,等待;False,不等待然後報錯 maxusage=None, # 一個鏈接最多被重覆使用的次數,None表示無限制 setsession=[], # 開始會話前執行的命令列表。 ping=0, # ping MySQL服務端,檢查是否服務可用。 host='127.0.0.1', port=3306, user='root', password='123456', database='youku', charset='utf8', autocommit = True )DBUtils,配置模板
def func(): ... conn = POOL.connection() ...
#!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2018/05/17 8:25 # @Author : MJay_Lee # @File : 列表推導式.py # @Contact : [email protected] egg_list = [] for i in range(10): egg_list.append('egg%s' % i) print(egg_list) # ['egg0', 'egg1', 'egg2', 'egg3', 'egg4', 'egg5', 'egg6', 'egg7', 'egg8', 'egg9'] egg_list2 = ['egg%s' % i for i in range(10)] print(egg_list2)列表推導式
class Foo: x=1 def __init__(self,y): self.y=y def __getattr__(self, item): print('----> from getattr:你找的屬性不存在') def __setattr__(self, key, value): print('----> from setattr') # self.key=value #這就無限遞歸了,你好好想想 # self.__dict__[key]=value #應該使用它 def __delattr__(self, item): print('----> from delattr') # del self.item #無限遞歸了 self.__dict__.pop(item) #__setattr__添加/修改屬性會觸發它的執行 f1=Foo(10) print(f1.__dict__) # 因為你重寫了__setattr__,凡是賦值操作都會觸發它的運行,你啥都沒寫,就是根本沒賦值,除非你直接操作屬性字典,否則永遠無法賦值 f1.z=3 print(f1.__dict__) #__delattr__刪除屬性的時候會觸發 f1.__dict__['a']=3#我們可以直接修改屬性字典,來完成添加/修改屬性的操作 del f1.a print(f1.__dict__) #__getattr__只有在使用點調用屬性且屬性不存在的時候才會觸發 f1.xxxxxx__getattr__,__setattr__
補充:
class Foo: def __init__(self,x): self.x=x def __getattr__(self, item): print('執行的是我') # return self.__dict__[item] def __getattribute__(self, item): print('不管是否存在,我都會執行') raise AttributeError('哈哈') f1=Foo(10) f1.x f1.xxxxxx #當__getattribute__與__getattr__同時存在,只會執行__getattrbute__,除非__getattribute__在執行過程中拋出異常AttributeError區分__getattr__,__getattribute__
反射(update和save兩個功能代碼里,拼接SQL語句時,給參數賦值時時需要用上):
def getattr(object, name, default=None): # known special case of getattr """ getattr(object, name[, default]) -> value Get a named attribute from an object; getattr(x, 'y') is equivalent to x.y. When a default argument is given, it is returned when the attribute doesn't exist; without it, an exception is raised in that case. """ pass def setattr(x, y, v): # real signature unknown; restored from __doc__ """ Sets the named attribute on the given object to the specified value. setattr(x, 'y', v) is equivalent to ``x.y = v'' """ pass def delattr(x, y): # real signature unknown; restored from __doc__ """ Deletes the named attribute from the given object. delattr(x, 'y') is equivalent to ``del x.y'' """ passgetattr及其它相關屬性
class BlackMedium: feature='Ugly' def __init__(self,name,addr): self.name=name self.addr=addr def sell_house(self): print('%s 黑中介賣房子啦,傻逼才買呢,但是誰能證明自己不傻逼' %self.name) def rent_house(self): print('%s 黑中介租房子啦,傻逼才租呢' %self.name) b1=BlackMedium('萬成置地','回龍觀天露園') #檢測是否含有某屬性 print(hasattr(b1,'name')) print(hasattr(b1,'sell_house')) #獲取屬性 n=getattr(b1,'name') print(n) func=getattr(b1,'rent_house') func() # getattr(b1,'aaaaaaaa') #報錯 print(getattr(b1,'aaaaaaaa','不存在啊')) #設置屬性 setattr(b1,'sb',True) setattr(b1,'show_name',lambda self:self.name+'sb') print(b1.__dict__) print(b1.show_name(b1)) #刪除屬性 delattr(b1,'addr') delattr(b1,'show_name') delattr(b1,'show_name111')#不存在,則報錯 print(b1.__dict__)類與對象的四個操作屬性示例
操作類與對象的屬性的補充:
class Foo: def __del__(self): print('執行我啦') f1=Foo() del f1 print('------->') #輸出結果 執行我啦 -------> ----------------------以下是另一種情況 class Foo: def __del__(self): print('執行我啦') f1=Foo() # del f1 print('------->') #輸出結果 -------> 執行我啦 典型的應用場景: 創建資料庫類,用該類實例化出資料庫鏈接對象,對象本身是存放於用戶空間記憶體中,而鏈接則是由操作系統管理的,存放於內核空間記憶體中 當程式結束時,python只會回收自己的記憶體空間,即用戶態記憶體,而操作系統的資源則沒有被回收,這就需要我們定製__del__,在對象被刪除前向操作系統發起關閉資料庫鏈接的系統調用,回收資源析構函數,__del__方法
format_dict={ 'nat':'{obj.name}-{obj.addr}-{obj.type}',#學校名-學校地址-學校類型 'tna':'{obj.type}:{obj.name}:{obj.addr}',#學校類型:學校名:學校地址 'tan':'{obj.type}/{obj.addr}/{obj.name}',#學校類型/學校地址/學校名 } class School: def __init__(self,name,addr,type): self.name=name self.addr=addr self.type=type def __repr__(self): return 'School(%s,%s)' %(self.name,self.addr) def __str__(self): return '(%s,%s)' %(self.name,self.addr) def __format__(self, format_spec): # if format_spec if not format_spec or format_spec not in format_dict: format_spec='nat' fmt=format_dict[format_spec] return fmt.format(obj=self) s1=School('oldboy1','北京','私立') print('from repr: ',repr(s1)) print('from str: ',str(s1)) print(s1) ''' str函數或者print函數--->obj.__str__() repr或者互動式解釋器--->obj.__repr__() 如果__str__沒有被定義,那麼就會使用__repr__來代替輸出 註意:這倆方法的返回值必須是字元串,否則拋出異常 ''' print(format(s1,'nat')) print(format(s1,'tna')) print(format(s1,'tan')) print(format(s1,'asfdasdffd'))自定義列印格式,__str__方法
#!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2018/04/18 17:47 # @Author : MJay_Lee # @File : 單例.py # @Contact : [email protected] # 基於元類實現單例模式 # 單例:即單個實例,指的是同一個類實例化多次的結果指向同一個對象,用於節省空間(場景:假若從配置文件中讀取配置來進行實例化,在配置相同的情況下,就沒必要重覆產生對象浪費記憶體了) # 方式一:定義一個類方法實現單例模式 # import setting # # class Mysql: # instance = None # def __init__(self,host,port): # self.host = host # self.port = port # # @classmethod # def from_conf(self): # if not Mysql.instance: # res = Mysql(setting.HOST, setting.PORT) # Mysql.instance = res # return Mysql.instance # # # con1 = Mysql('127.0.0.1',80) # <__main__.Mysql object at 0x000000A9F7FC7978> # # con2 = Mysql('127.0.0.1',80) # <__main__.Mysql object at 0x000000A9F7FD8710> # # con3 = Mysql('127.0.0.1',80) # <__main__.Mysql object at 0x000000A9F7E09C88> # # print(con1,con2,con3) # # # # # con1 = Mysql.from_conf() # <__main__.Mysql object at 0x000000BB72BA4DD8> # # con2 = Mysql.from_conf() # <__main__.Mysql object at 0x000000BB72BA4E48> # # con3 = Mysql.from_conf() # <__main__.Mysql object at 0x000000BB72BA4E80> # # print(con1,con2,con3) # # con1 = Mysql.from_conf() # <__main__.Mysql object at 0x000000BD5BBA4DD8> # con2 = Mysql.from_conf() # <__main__.Mysql object at 0x000000BD5BBA4DD8> # con3 = Mysql.from_conf() # <__main__.Mysql object at 0x000000BD5BBA4DD8> # print(con1 is con2 is con3) # True # # 方式二:定製元類實現 # # 若從配置文件取相同配置產生對象則實現單例,若傳值則新建對象 import setting class Mymeta(type): def __init__(self,name,bases,dic): # 定義類Mysql時就觸發 # 事先從配置文件中取配置來造一個Mysql的實例出來 self.__instance = object.__new__(self) # 產生對象 self.__init__(self.__instance,setting.HOST,setting.PORT) # 初始化對象 #上述兩步可合併下麵一步 # self.__instance = super().__call__(*args,**kwargs) super().__init__(name,bases,dic) def __call__(self, *args, **kwargs): # Mysql(...)時觸發 if args or kwargs: # Mymeta類的對象括弧內傳值則新建obj,否則返回self.__instance obj = object.__new__(self) self.__init__(obj,*args,**kwargs) return obj return self.__instance # Mysql = Mymeta('Mysql',(obj,),class_dic) class Mysql(metaclass=Mymeta): def __init__(self,host,port): self.host = host self.port = port con1 = Mysql() con2 = Mysql() # con3 = Mysql() # <__main__.Mysql object at 0x0000008BA7E24DD8> # con4 = Mysql('127.0.0.1',80) # <__main__.Mysql object at 0x0000004B4B904EF0>,若Mymeta類的對象(Mysql)括弧內傳值則新建obj # print(con4) # True # 裝飾器實現單例 # import setting # # def single_obj(cls): # __instance = cls(setting.HOST,setting.PORT) # def wrapper(*args, **kwargs): # if args or kwargs: # obj = cls(*args, **kwargs) # return obj # return __instance # return wrapper # # @single_obj # class Mysql: # def __init__(self,host,port): # self.host = host # self.port = port # # con1 = Mysql() # <__main__.Mysql object at 0x0000001F978D9C88> # con2 = Mysql() # <__main__.Mysql object at 0x0000001F978D9C88> # con3 = Mysql('127.0.0.1',80) # <__main__.Mysql object at 0x0000001F98AE4DD8> # # print(con1 is con2) # True三個方法實現單例--示例
ORM簡介
ORM即Object Relational Mapping,全稱對象關係映射。
當我們需要對資料庫進行操作時,勢必需要通過連接數據、調用sql語句、執行sql語句等操作,ORM將資料庫中的表,欄位,行與我們面向對象編程的類及其方法,屬性等一一對應,即將該部分操作封裝起來,程式猿不需懂得sql語句即可完成對資料庫的操作。
一、知識儲備:
1、在實例化一個user對象的時候,可以user=User(name='lqz',password='123')
2 也可以 user=User()
user['name']='lqz'
user['password']='123'
3 也可以 user=User()
user.name='lqz'
user.password='password'
前兩種,可以通過繼承字典dict來實現,第三種,用getattr和setattr:
__getattr__ 攔截點號運算。當對未定義的屬性名稱和實例進行點號運算時,就會用屬性名作為字元串調用這個方法。如果繼承樹可以找到該屬性,則不調用此方法
__setattr__會攔截所有屬性的的賦值語句。如果定義了這個方法,self.arrt = value 就會變成self,__setattr__("attr", value).這個需要註意。當在__setattr__方法內對屬性進行賦值是,不可使用self.attr = value,因為他會再次調用self,__setattr__("attr", value),則會形成無窮遞歸迴圈,最後導致堆棧溢出異常。應該通過對屬性字典做索引運算來賦值任何實例屬性,也就是使用self.__dict__['name'] = value
二、定義Model基類
# 在ModelsMetaclass中自定義攔截實例化對象的方法 class Models(dict,metaclass=ModelsMetaclass): # k,v形式的值 def __init__(self,**kwargs): super().__init__(**kwargs) # 寫存 def __setattr__(self, key, value): self[key] = value # 讀取 def __getattr__(self, item): try: return self[item] except KeyError: raise ('沒有該屬性')
三、定義Field
資料庫中每一列數據,都有:列名,列的數據類型,是否是主鍵,預設值
# 表示一個列:列名,列的類型,列的主鍵和預設值 class Field: 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 class StringField(Field): def __init__(self,name=None,column_type='varchar(200)',primary_key=False,default=None): super().__init__(name,column_type,primary_key,default) class IntegerField(Field): def __init__(self,name=None,column_type='int',primary_key=False,default=None): super().__init__(name,column_type,primary_key,default)
四、定義元類
資料庫中的每個表,都有表名,每一列的列名,以及主鍵是哪一列
既然我要用資料庫中的表,對應這一個程式中的類,那麼我這個類也應該有這些類屬性
但是不同的類這些類屬性又不盡相同,所以我應該怎麼做?在元類里攔截類的創建過程,然後把這些東西取出來,放到類裡面
class ModelsMetaclass(type): def __new__(cls,name,bases,attrs): if name == 'Models': # return type.__new__(cls, name, bases, attrs) table_name = attrs.get('table_name', None) #字典取值,中括弧或.get if not table_name: table_name = name primary_key = None mappings = dict() for k, v in attrs.items(): if isinstance(v, Field): # v 是不是Field的對象 mappings[k] = v if v.primary_key: # v是基類對象,即判斷該欄位的主鍵 # 找到主鍵 if primary_key: raise TypeError('主鍵重覆:%s' % k) primary_key = k for k in mappings.keys(): attrs.pop(k) # 執行完此步後,attrs中只剩餘有__屬性__ if not primary_key: raise TypeError('沒有主鍵') attrs['table_name'] = table_name attrs['primary_key'] = primary_key attrs['mappings'] = mappings return type.__new__(cls, name, bases, attrs)
五、基於pymysql的資料庫操作類(單例)
#!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2018/05/15 10:44 # @Author : MJay_Lee # @File : mysql_singleton.py # @Contact : [email protected] import pymysql class Mysql_interface: __instense = None def __init__(self): self.conn = pymysql.connect( host = '127.0.0.1', port = 3306, user = 'root', password = '123456', charset = 'utf8', database = 'youku', autocommit = True ) self.cursor = self.conn.cursor(cursor=pymysql.cursors.DictCursor) def close_db(self): self.cursor.close() self.conn.close() def select(self,sql,args): self.cursor.execute(sql,args) re = self.cursor.fetchall() return re def execute(self,sql,args): try: self.cursor.execute(sql,args) affected = self.cursor.rowcount except BaseException as e: print(e) return affected @classmethod def singleton(cls): if not cls.__instense: cls.__instense = cls() return cls.__instense if __name__ == '__main__': ms = Mysql_interface() re = ms.select('select * from user where id = %s',1) print(re)
六、繼續Models基類
Models類是所有要對應資料庫表類的基類,所以,Models的元類應該是咱們上面寫的那個
而每個資料庫表對應類的對象,都應該有查詢、插入、保存,方法
所以:
# 在ModelsMetaclass中自定義攔截實例化對象的方法 class Models(dict,metaclass=ModelsMetaclass): # k,v形式的值 def __init__(self,**kwargs): super().__init__(**kwargs) # 寫存 def __setattr__(self, key, value): self[key] = value # 讀取 def __getattr__(self, item): try: return self[item] except KeyError: raise ('沒有該屬性') @classmethod def select_one(cls,**kwargs): ''' 查一條 :param kwargs: :return: ''' key = list(kwargs.keys())[0] value = kwargs[key] # select * from user where id=%s sql = 'select * from %s where %s =?' % (cls.table_name,key) sql = sql.replace('?','%s') ms = mysql_singleton.Mysql_interface().singleton() re = ms.select(sql,value) # 得到re字典對象 if re: # attrs = {'name':'lmj','password':123} # User(**attrs) # 相當於 User(name='lmj',password=123) return cls(**re[0]) else: return @classmethod def select_many(cls, **kwargs): ''' 查多條 :param kwargs: :return: ''' ms = mysql_singleton.Mysql_interface().singleton() if kwargs: key = list(kwargs.keys())[0] value = kwargs[key] sql = 'select * from %s where %s =?' % (cls.table_name, key) sql = sql.replace('?', '%s') re = ms.select(sql, value) # 得到re字典對象 else: sql = 'select * from %s' % (cls.table_name) re = ms.select(sql) if re: obj_list = [cls(**r) for r in re] return obj_list else: return def update(self): ms = mysql_singleton.Mysql_interface().singleton() # update user set name = ?,password = ? where id = ? filed_data = [] # name = ?,password = ? pr = None args = [] # 欄位的值 for k,v in self.mappings.items(): if v.primary_key: pr = getattr(self,v.name,v.default) else: filed_data.append(v.name + '=?') args.append(getattr(self,v.name,v.default)) sql = 'update %s set %s where %s = %s' % (self.table_name,','.join(filed_data),self.primary_key,pr) sql = sql.replace('?','%s') ms.execute(sql,args) def save(self): ms = mysql_singleton.Mysql_interface().singleton() # insert into user(name,password) values (?,?) field_data = [] args = [] value_data = [] for k,v in self.mappings.items(): if