参悟python元类(又称metaclass)系列实战(三)
写在前面
在上一章节参悟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的方法: getValue - 
如果获取不到, 提供一个试着返回其默认值的方法: getValueOrDefault
 
 - 
 - 
初版实现如下
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 = 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__') 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 = 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')
    user_status = IntegerField(default=0, ddl='tinyint(1)')
    image = StrField(default='about:blank', ddl='varchar(500)')
    created_at = DateTimeField(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='xiaoming@qq.com', '****', name='小明') # 这里其实是调用了Model.__init__, 参数类型 **kw print(xiaoMing.uid) # TODO: 如何把小明的信息写入到数据库中呢? xiaoMing.insert() # 将在后面的章节中加入 
总结
- 
定义了一个
Dict类, 比dict多支持了.的访问形式, 外加一个toDict方法; - 
抽象出一个
Model类, 用以概括所有table的特性, 本章只处理了概括table.column的特性(获取值/默认值); - 
定义第一版元类
ModelMetaClass, 作用在创建Model及其子类的过程中, 使得它们具有"__table__: 表名,__mappings__: column name --> Field class,__primaryKey__: 主键,__fields__: 除主键外的其他column name" 的属性; - 
计划在后续的章节加入数据保存链接的功能;
 
                    
                
                
            
        
浙公网安备 33010602011771号