ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

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

2020-11-05 17:00:55  阅读:265  来源: 互联网

标签:__ python self value 元类 Dict key ddl metaclass


写在前面

在上一章节参悟python元类(又称metaclass)系列实战(二)简单铺垫了下code如何映射到数据库的table;
本节内容我们再增强下字段的映射(如默认值、主键), 抽象出更抽象的元类, 后面再实现select等操作;
有误的地方恳请大神指正下。

热身预备

  • 我们都知道dict类型的获取value的写法(dict[key]), 比较丑陋

  • 现在我们自定义一个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
    

能复用则复用

  • 考虑到数据库里肯定不止一张表, 所以我们需要抽象出一个类, 用来概括所有表的特征, 粗略设计如下

    1. 可以继承Dict类, 使其具有key value的特征

    2. 提供一个可以根据key获取value的方法

    3. 再提供一个跟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)')
  • 举例

    """映射到行"""
    xiaoMing = Users(uid=103, email='xiaoming@qq.com', '****', name='小明')  # 这里其实是调用了Dict.__init__, 参数类型 **kw
    
    print(xiaoMing.uid)
    
    # TODO: 如何把小明的信息写入到数据库中呢?
    xiaoMing.save()    # 将在下一章节加入
    

标签:__,python,self,value,元类,Dict,key,ddl,metaclass
来源: https://www.cnblogs.com/z417/p/13931424.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有