寫在前面
在上一章節參悟python元類(又稱metaclass)系列實戰(二)簡單鋪墊了下code如何映射到資料庫的table;
本節內容我們再增強下欄位的映射(如默認值、主鍵), 抽象出更抽象的元類, 后面再實作select等操作;
有誤的地方懇請大神指正下,
熱身預備
-
我們都知道
dict型別的獲取value的寫法(dict[key]), 比較丑陋 -
現在我們自定義一個
dict的子類Dict, 使其可以Dict.key的形式獲取valueclass Dict(dict): '''dict子類, 擴展了value的訪問方式; 還支持傳入兩個長度相等的tuple, 組成key-value''' def __init__(self, names=(), values=(), **kw): ''' @names: tuple形式的key集合 @values: tuple形式的value集合 ''' super().__init__(**kw) for k, v in zip(names, values): self[k] = v def __getattr__(self, item): ''' 當試圖訪問實體不存在的屬性時, 會自動呼叫該方法; 訪問方式就是'點' ''' try: return self[item] except KeyError: raise AttributeError(r"'Dict' object has no attribute '%s'" % item) def __setattr__(self, key, value): ''' 當試圖給不存在的屬性賦值時, 會自動呼叫該方法 ''' self[key] = value -
再定義一個方法, 可以把
dict型別轉為Dict型別def toDict(d: dict): D = Dict() for k, v in d.items(): D[k] = toDict(v) if isinstance(v, dict) else v return D -
熱身完畢, 但
toDict有一種情況無法轉為.的形式訪問d = { 'k1': [{'kk1':'vv1'}] } # 無法以 d.k1[0].kk1訪問vv1
能復用則復用
-
考慮到資料庫里肯定不止一張表, 所以我們需要抽象出一個類, 用來概括所有表的特征, 粗略設計如下
-
可以繼承
Dict類, 使其具有key value的特征 -
提供一個可以根據
key獲取value的方法 -
如果獲取不到, 就試著回傳其默認值的方法
-
-
初版實作如下
class Model(Dict): def __init__(self, **kw): super().__init__(**kw) # 呼叫父類Dict的方法 def getValue(self, key): return getattr(self, key, None) def getValueOrDefault(self, key): value = https://www.cnblogs.com/z417/p/getattr(self, key, None) if value is None: # TODO: 設定成default pass return value -
因為每張表的欄位名和型別都不一樣, 而
Model又得能概括所有表的欄位, 因此就要求能對Model類動態創建, 自然就想到元類可以幫我們實作class ModelMetaClass(type): def __new__(cls, name, bases, attrs): if name == 'Model': # 當出現與'Model'同名的類時, 直接創建這類 return type.__new__(cls, name, bases, attrs) # 定義表名: 要么在類中定義__table__屬性, 否則與類名相同 tableName = attrs.get('__table__', None) or name print(f'建立映射關系: {name}類 --> {tableName}表') mappings = Dict() # 存盤column與Field 子類的對應關系, Field在上一章中定義的, 忘了回去翻 fields = [] # 用來存盤除主鍵以外的所有欄位名 primaryKey = None # 用來記錄主鍵欄位的名字, 初始沒有 for k, v in attrs.items(): # 遍歷所有屬性, 即映射表的欄位, 讀不懂請回看第二章 Users 的定義 if isinstance(v, Field): # Field類, 所有欄位型別的父類 print(f'建立映射... column: {k} ==> class: {v}') mappings[k] = v if v.primaryKey: # 判斷欄位是否被設定成了主鍵 if primaryKey: # 因為一張表只能有一個主鍵 raise Exception(f'Duplicate primary key for field {k}') primaryKey = k else: fields.append(k) if not primaryKey: # 這里做了一步強制要求設定主鍵, 你也可以去掉 raise Exception(f'請給表{tableName}設定主鍵') for k in mappings.keys(): # 洗掉原屬性, 避免實體的屬性遮蓋類的同名屬性, 況且我們已經保存到 mappings 中了, 不怕丟 attrs.pop(k) # 接下來給本元類(ModelMetaClass)創建的class(如 Model)設定私有屬性 attrs['__mappings__'] = mappings attrs['__table__'] = tableName attrs['__primaryKey__'] = primaryKey attrs['__fields__'] = fields return type.__new__(cls, name, bases, attrs) -
第二版Model, 即完成初版的"TODO"
class Model(Dict, metaclass=ModelMetaClass): """指定metaclass, 以實作動態定制""" def __init__(self, **kw): super().__init__(**kw) def getValue(self, key): return getattr(self, key, None) def getValueOrDefault(self, key): value = https://www.cnblogs.com/z417/p/getattr(self, key, None) if value is None: field = self.__mappings__[key] # 從所有column中獲取value if field.default is not None: # 如果default指向是方法(如time.time), 則呼叫方法獲取其值; 否則直接賦值 value = field.default() if callable(field.default) else field.default print(f'using defalut value for {key}: {value}') setattr(self, key, value) # 其實是調 Dict.__setattr__, 以支持用"."訪問 return value
給上一章的 Users 加入父類Model
"""映射到表 Users; 同理定義其他映射關系 """
class Users(Model):
"""
繼承自Model, 這樣Users就有了Dict特性, 同時在實體化Users時, 又會以ModelMetaClass定制的特性創建
"""
uid = IntegerField(primaryKey=True, ddl='int(11)')
email = StrField(ddl='varchar(50)')
passwd = StrField(ddl='char(32)')
admin = IntegerField(default=0, ddl='tinyint(1)')
name = StrField(ddl='varchar(50)')
birthday = DateTimeField(ddl='DATE')
image = StrField(default='about:blank', ddl='varchar(500)')
created_at = DateTimeField(default='0000-00-00 00:00:00', ddl='timestamp')
updated_at = DateTimeField(ddl='timestamp')
created_by = IntegerField(ddl='int(11)')
updated_by = IntegerField(ddl='int(11)')
is_deleted = IntegerField(default=0, ddl='tinyint(1)')
-
舉例
"""映射到行""" xiaoMing = Users(uid=103, email='[email protected]', '****', name='小明') # 這里其實是呼叫了Model.__init__, 引數型別 **kw print(xiaoMing.uid) # TODO: 如何把小明的資訊寫入到資料庫中呢? xiaoMing.save() # 將在下一章節加入
總結
-
定義了一個
Dict類, 比dict多支持了.的訪問形式, 外加一個toDict方法; -
抽象出一個
Model類, 用以概括所有table的特性, 本章只處理了概括table.column的特性(獲取值/默認值); -
定義第一版元類
ModelMetaClass, 作用在創建Model及其子類的程序中, 使得它們具有"__table__: 表名,__mappings__: column name --> Field class,__primaryKey__: 主鍵,__fields__: 除主鍵外的其他column name" 的屬性; -
計劃在下一章節加入資料保存/修改的功能;
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/203905.html
標籤:Python
上一篇:關于frida的例外
