zoukankan      html  css  js  c++  java
  • 参悟python元类(又称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的方法: getValue

      3. 如果获取不到, 提供一个试着返回其默认值的方法: 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()    # 将在后面的章节中加入
      

    总结

    1. 定义了一个Dict类, 比dict多支持了.的访问形式, 外加一个toDict方法;

    2. 抽象出一个Model类, 用以概括所有table的特性, 本章只处理了概括table.column的特性(获取值/默认值);

    3. 定义第一版元类ModelMetaClass, 作用在创建Model及其子类的过程中, 使得它们具有"__table__: 表名, __mappings__: column name --> Field class, __primaryKey__: 主键, __fields__: 除主键外的其他column name" 的属性;

    4. 计划在后续的章节加入数据保存链接的功能;

    5. 下一章"参悟python元类(又称metaclass)系列实战(四)"。

  • 相关阅读:
    重构之美之一改造面向过程式设计(转)
    arcgis_server for java的问题
    Using Javascript to display RSS
    JavaScript开发规范要求(转载)
    恭贺今天开张大吉!
    今天为打乙肝疫苗抽血。
    人类登上月球
    c++读写文件流
    检索Repeater中的checkbox
    Z:如何轻松做好网站外链的十大技巧
  • 原文地址:https://www.cnblogs.com/z417/p/13931424.html
Copyright © 2011-2022 走看看