• 不会metaclass你居然敢说自己会Python?


    python是个神奇的语言,神奇的地方在于一切皆对象,所以没有对象的你在这里应该可以找到真爱。

    什么叫一切皆对象?什么是对象?它和类class有什么关系?

    在python里我们定义类的时候往往会

    class A(object):
        pass
    
    • 1
    • 2

    这个object我们都知道是父类,任何类往上倒几代,祖先都是object。

    那我们定义的这些类的类型是什么呢?

    print(type(A))
    <class 'type'>
    
    • 1
    • 2

    类的type是type,哈哈哈。

    python里类型的尽头是type,不信你看

    a = 1
    print(type(a))  # int
    print(type(type(a)))  # type
    
    • 1
    • 2
    • 3

    因此有如下说法:

    • type为对象的顶点,所有对象都创建自type。

    • object为类继承的顶点,所有类都继承自object。

    我们知道在定义类的时候需要定义__init__方法,用于在实例化的时候进行参数初始化的,但真正的对象创建却不是在这个方法里,而是在__new__里进行定义,因此我们可以进行__new__方法的重构而对对象创建的过程进行干预。

    铺垫了半天metaclass到底是干嘛的?既然类是对象,大家想一下我们在定义类(对象创建)的时候能不能干涉一下?这就是metaclass的作用。看个例子

    class A(type):
        def __new__(cls, *args, **kwargs):
            print('metaclass new')
            return type.__new__(cls, *args, **kwargs)
    
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            print('metaclass init')
    
    class B(metaclass=A):
        def __init__(self):
            print('B init')
    
    print('==start==')
    b = B()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    输出

    metaclass new
    metaclass init
    ==start==
    B init
    
    • 1
    • 2
    • 3
    • 4

    大家注意一下打印的顺序,在还没有实例化b的时候,metaclass就已经执行了,也就是说class B生成的时候(不是实例化)受到了class A的影响。

    那这到底有啥用?什么场景会用到?

    ORM(Object Relational Mapping)对象关系映射

    我们在使用Django框架的时候,不可避免会涉及到数据的管理操作(CRUD),而且需要与数据库进行连接操作。而关系型的数据库需要编写原生的SQL语句,那么如果我们的代码里包含大量的sql语句会严重影响开发效率,且变得难以维护。

    • 应用开发程序员需要耗费很大精力去优化SQL语句
    • 各个数据库之间的SQL差异导致进行数据库迁移时难以适配

    因此Django提出了ORM概念,在SQL语句上进行了面向对象的封装,现在我们来实现一下:

    # 一、首先定义Field类,它负责保存数据库表的字段名和字段类型
    class Field:
        def __init__(self, name, column_type):
            self.name = name
            self.colmun_type = column_type
    
        def __str__(self):
            return f'<{self.__class__.__name__}:{self.name}>'
    
    
    class StringField(Field):
        def __init__(self, name):
            super().__init__(name, 'varchar(100)')
    
    
    class IntegerField(Field):
        def __init__(self, name):
            super().__init__(name, 'bigint')
    
    
    # 二、定义元类,控制Model对象的创建
    class ModelMetaClass(type):
        def __new__(cls, name, bases, attrs):
            if name == 'Model':
                return super().__new__(cls, name, bases, attrs)
            mappings = dict()
            for k, v in attrs.items():
                # 保持类属性和列的映射关系到mappings字典
                if isinstance(v, Field):
                    print(f'Found mapping:{k}==>{v}')
                    mappings[k] = v
            for k in mappings.keys():  # 将类属性移除,是定义的类字段不污染User类属性,只在实例中可以访问这些key
                attrs.pop(k)
            attrs['__table__'] = name.lower()  # 假设表名为类名的小写,创建类时添加一个__table__属性
            attrs['__mappings__'] = mappings  # 保持属性和列的关系映射,创建类时添加一个__mappings__属性
            return super().__new__(cls, name, bases, attrs)
    
    
    # 三、Model基类
    class Model(dict, metaclass=ModelMetaClass):
        def __init__(self, **kwargs):
            super().__init__(**kwargs)
    
        def __getattr__(self, key):
            try:
                return self[key]
            except KeyError:
                raise AttributeError(f"'Model' object has no attribute '{key}'")
    
        def __setattr__(self, key, value):
            self[key] = value
    
        def save(self):
            fields = []
            params = []
            args = []
            for k, v in self.__mappings__.items():
                fields.append(v.name)
                params.append('?')
                args.append(getattr(self, k, None))
            sql = f"insert into {self.__table__} {','.join(fields)} values ({','.join(params)})"
            print(f'SQL:{sql}')
            print(f'ARGS:{str(args)}')
    
    
    # 我们想创建类似Django的ORM,只要定义字段就可以实现对数据库表和字段的操作
    # 最后、我们使用定义好的ORM接口,使用起来非常简单
    class User(Model):
        id = IntegerField('id')
        name = StringField('username')
        email = StringField('email')
        password = StringField('password')
    
    
    user = User(id=1, name='Job', email='job@test.com', password='pw')
    user.save()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76

    输出

    Found mapping:id==><IntegerField:id>
    Found mapping:name==><StringField:username>
    Found mapping:email==><StringField:email>
    Found mapping:password==><StringField:password>
    SQL:insert into user id,username,email,password values (?,?,?,?)
    ARGS:[1, 'Job', 'job@test.com', 'pw']
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    Model基类通过元类ModelMetaClass控制对象的创建,然后通过定义统一的save接口从而在用户定义数据库和操作的时候会十分容易且人性化,然后我们再将update、delete和search接口实现一下即可。

    元类同时实现__getattr____setattr__方法,可以直接引用普通字段

    user.id = 1
    print(user.id)
    
    • 1
    • 2

    思维发散

    是不是可以不用metaclass,直接在Model里重构__new__,内容与ModelMetaClass__new__一致,能否达到一样的效果?

    不行的,只有metaclass的__new__才能对子类的传参进行修改,其他类是不可以的,因此只能使用metaclass

    动态加载

    配置文件与类相结合,通过配置文件进行类实例化,不同的配置文件可以进行不同的实例化,这里的yaml.YAMLObject的实现也是使用了metaclass。

    import time
    import yaml
    
    
    class Monster(yaml.YAMLObject):
        yaml_tag = '!yaml'
    
        def __init__(self, name):
            self.name = name
    
    
    while 1:
        with open('dynamicLoad_demo.yaml', 'r') as f:
            cls = yaml.load(f, Loader=yaml.Loader)
        print(cls.name)
        time.sleep(2)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    同时构建配置文件dynamicLoad_demo.yaml

    !yaml
    name:
      zzr
    
    • 1
    • 2
    • 3

    代码跑起来,过一会修改配置文件中的name字段,可以看到输出同步发生变化。

    参考

    python中的metaclass

  • 相关阅读:
    Maven除了管理包还能干嘛?
    嵌入式开发中Cache问题的解决方法
    【LeetCode每日一题】——面试题17.16.按摩师
    微信小程序 springboot新冠疫苗预约系统#计算机毕业设计
    ARM Linux DIY(十四)摄像头捕获画面显示到屏幕
    使用jQuery删除指定位置的数组元素
    持续交付知易行难,想做成这事你要理解这几个关键点
    Cheat Engine CE v7.5 安装教程(专注于游戏的修改器)
    ElasticSearch(超详细解说)[springBoot整合ES并简单实现增删改查]
    如何从存档服务器上完全删除PDM用户
  • 原文地址:https://blog.csdn.net/z13653662052/article/details/124873108