OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 2957|回复: 0

参悟python元类(又称metaclass)系列实战

[复制链接]

143

主题

145

帖子

0

精华

高级会员

Rank: 4

积分
585
金钱
585
注册时间
2020-5-25
在线时间
42 小时
发表于 2020-11-5 17:04:46 | 显示全部楼层 |阅读模式
  • 现在我们自定义一个dict的子类Dict, 使其可以Dict.key的形式获取value
    class 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的方法
    • 再提供一个跟1类似, 但如果获取不到还可以返回其默认值的方法

  • 初版实现如下
    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__', 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 = 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 类"""映射到表 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)')
参考文本学习视频资料:http://www.makeru.com.cn/live/1392_1164.html?s=143793
正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2024-6-9 04:35

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表