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

参悟python元类(又称metaclass)系列实战(三)[Python基础]

写在前面

在上一章节参悟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. 如果获取不到, 就试着返回其默认值的方法

  • 初版实现如下

    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 加入父类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="xiaoming@qq.com", "****", name="小明") # 这里其实是调用了Model.__init__, 参数类型 **kw

    print(xiaoMing.uid)

    # TODO: 如何把小明的信息写入到数据库中呢?

    xiaoMing.save() # 将在下一章节加入

总结

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

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

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

  4. 计划在下一章节加入数据保存/修改的功能;

以上是 参悟python元类(又称metaclass)系列实战(三) 的全部内容, 来源链接: utcz.com/z/537779.html

回到顶部