• 十二、元类和ORM的实现


    一、元类

    元类是面向对象编程中的一个概念,用于创建类的类。在Python中,一切都是对象,包括类本身。而元类就是用来创建这些类的。
    元类提供了控制类创建过程的功能,它可以在类被定义时自动执行一些操作。可以将元类视为类的模板,它定义了类的行为、属性和方法。

    1.1 动态创建类

    因为类也是对象,你可以在运行时动态的创建它们,就像其他任何对象一样。首先,你可以在函数中创建类,使用class关键字即可,Python解释器在执行的时候就会创建一个对象。

    def choose_class(name):
        if name == 'foo':
            # 定义一个Foo类,同时会创建一个Foo的对象, 注意不是Foo的实例对象.
            class Foo(object):
                pass
    
            return Foo  # 注意:返回的是类, 而不是类的实例
        else:
            class Bar(object):
                pass
    
            return Bar
    
    
    if __name__ == '__main__':
        foo_class = choose_class('foo')
        print("foo_class===> " + str(foo_class))
    
        # 创建Foo类的实例对象
        foo_obj = foo_class()
        print("foo_obj===> " + str(foo_obj))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    输出结果:

    foo_class===> <class '__main__.choose_class..Foo'>
    foo_obj===> <__main__.choose_class.<locals>.Foo object at 0x101208a60>
    
    • 1
    • 2

    但这还不够动态,因为你仍然需要自己编写整个类的代码。由于类也是对象,所以它们必须是通过什么东西来生成的才对。

    1.2 使用type创建类

    当你使用class关键字时,Python解释器自动创建这个对象。但就和Python中的大多数事情一样,Python仍然提供给你手动处理的方法。内建函数type除了可以查看对象的类型是什么, 它还可以用来动态的创建类.
    语法如下:

    type(类名, 由父类名称组成的元组(针对继承的情况,可以为空),包含属性和方法的字典(名称和值))
    
    • 1
    • 参数2是元组中是父类的名字,而不是字符串
    • 参数3是添加的类属性,并不是实例属性, 此外还能添加实例方法、类方法、静态方法。
      用法示例:
    # 定了一个Test类, 参数2指定继承object类,参数3指定类的属性
    Test = type("Test", (object,), {'bar':True})
    
    t = Test()
    print(t.bar)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    上面定义类的方式相当于:

    class Test(object):
        bar = True
    
    • 1
    • 2

    1.3 使用type创建带有方法的类

    @classmethod
    def class_method(cls, value):
        print("class_method:" + cls.class_attr + " " + value)
    
    
    @staticmethod
    def static_method(value):
        print("static_method:" + value)
    
    
    def obj_method(self):
        print("obj_method:" + self.class_attr)
    
    
    # 定义class
    TestClass = type("Test", (object,),
                     {'class_attr': 'hello', 'class_method': class_method, 'static_method': static_method,
                      'obj_method': obj_method})
    
    # 创建实例
    test = TestClass()
    # 调用实例方法
    test.obj_method()
    
    # 调用类方法
    TestClass.class_method('通过类名调用类方法')
    test.class_method('通过实例对象调用类方法')
    
    # 调用静态方法
    TestClass.static_method('通过类名调用静态方法')
    test.static_method('通过实例对象调用静态方法')
    
    • 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

    1.4 __metaclass__属性

    你可以在定义一个类的时候为其添加__metaclass__属性。

    class Foo(object):
        __metaclass__ = something…
        ...省略...
    
    • 1
    • 2
    • 3

    如果你这么做了,Python就会用元类来创建类Foo。小心点,这里面有些技巧。你首先写下class Foo(object),但是类Foo还没有在内存中创建。Python会在类的定义中寻找__metaclass__属性,如果找到了,Python就会用它来创建类Foo,如果没有找到,就会用内建的type来创建这个类。
    当你写如下代码时 :

    class Foo(Bar):
        pass
    
    • 1
    • 2

    Python做了如下的操作:

    1. Foo中有__metaclass__这个属性吗?如果是,Python会通过__metaclass__创建一个名字为Foo的类(对象)
    2. 如果Python没有找到__metaclass__,它会继续在Bar(父类)中寻找__metaclass__属性,并尝试做和前面同样的操作。
    3. 如果Python在任何父类中都找不到__metaclass__,它就会在模块层次中去寻找__metaclass__,并尝试做同样的操作。
    4. 如果还是找不到__metaclass__,Python就会用内置的type来创建这个类对象。
      现在的问题就是,你可以在__metaclass__中放置些什么代码呢?答案就是:可以创建一个类的东西。那么什么可以用来创建一个类呢?type。

    1.5 自定义元类

    元类的主要目的就是为了当创建类时能够自动地改变类。
    假设你想让你的模块里所有的类的属性都是大写形式。有好几种方法可以办到,但其中一种就是通过在模块级别设定__metaclass__。采用这种方法,这个模块中的所有类都会通过这个元类来创建,我们只需要告诉元类把所有的属性都改成大写形式就万事大吉了。
    幸运的是,__metaclass__实际上可以被任意调用,它并不需要是一个正式的类。所以,我们这里就先以一个简单的函数作为例子开始。
    (1) Python2中

    # -*- coding:utf-8 -*-
    def upper_attr(class_name, class_parents, class_attr):
        # class_name 会保存类的名字 Foo
        # class_parents 会保存类的父类 object
        # class_attr 会以字典的方式保存所有的类属性
    
        # 遍历属性字典,把不是__开头的属性名字变为大写
        new_attr = {}
        for name, value in class_attr.items():
            if not name.startswith("__"):
                new_attr[name.upper()] = value
    
        # 调用type来创建一个类
        return type(class_name, class_parents, new_attr)
    
    
    class Foo(object):
        __metaclass__ = upper_attr  # 设置Foo类的元类为upper_attr
        bar = 'bip'
    
    
    print(hasattr(Foo, 'bar')) # False
    print(hasattr(Foo, 'BAR')) # True
     
    f = Foo()
    print(f.BAR) # bip
    
    • 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

    (2) 在Python3中

    # -*- coding:utf-8 -*-
    def upper_attr(class_name, class_parents, class_attr):
        # 遍历属性字典,把不是__开头的属性名字变为大写
        new_attr = {}
        for name, value in class_attr.items():
            if not name.startswith("__"):
                new_attr[name.upper()] = value
    
        # 调用type来创建一个类
        return type(class_name, class_parents, new_attr)
    
    
    # 定义类的时候指定metaclass
    class Foo(object, metaclass=upper_attr):
        bar = 'bip'
    
    
    print(hasattr(Foo, 'bar')) # False
    print(hasattr(Foo, 'BAR')) # True
    
    f = Foo()
    print(f.BAR) # bip
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    现在让我们再做一次,这一次用一个真正的class来当做元类。

    # coding=utf-8
    
    class UpperAttrMetaClass(type):
        # __new__ 是在__init__之前被调用的特殊方法
        # __new__是用来创建对象并返回之的方法
        # 而__init__只是用来将传入的参数初始化给对象
        # 你很少用到__new__,除非你希望能够控制对象的创建
        # 这里,创建的对象是类,我们希望能够自定义它,所以我们这里改写__new__
        def __new__(cls, class_name, class_parents, class_attr):
            # 遍历属性字典,把不是__开头的属性名字变为大写
            new_attr = {}
            for name, value in class_attr.items():
                if not name.startswith("__"):
                    new_attr[name.upper()] = value
    
            # 方法1:通过'type'来做类对象的创建
            return type(class_name, class_parents, new_attr)
    
            # 方法2:复用type.__new__方法
            # return type.__new__(cls, class_name, class_parents, new_attr)
    
    
    # python3的用法
    class Foo(object, metaclass=UpperAttrMetaClass):
        bar = 'bip'
    
    
    # python2的用法
    # class Foo(object):
    #     __metaclass__ = UpperAttrMetaClass
    #     bar = 'bip'
    
    
    print(hasattr(Foo, 'bar'))  # 输出: False
    print(hasattr(Foo, 'BAR'))  # 输出:True
    
    f = Foo()
    print(f.BAR)  # 输出:'bip'
    
    • 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

    二、ORM的实现

    ORM 是 python编程语言后端web框架 Django的核心思想,“Object Relational Mapping”,即对象-关系映射,简称ORM。
    一个句话理解就是:创建一个实例对象,用创建它的类名当做数据表名,用创建它的类属性对应数据表的字段,当对这个实例对象操作时,能够对应MySQL语句。
    所谓的ORM就是让开发者在操作数据库的时候,能够像操作对象时通过xxxx.属性=yyyy一样简单,这是开发ORM的初衷。

    2.1 通过元类实现简单数据库insert操作

    class ModelMetaclass(type):
        def __new__(cls, name, bases, attrs):
            mappings = dict()
            # 判断是否需要保存
            for k, v in attrs.items():
                # 判断是否是指定的StringField或者IntegerField的实例对象
                if isinstance(v, tuple):
                    print('Found mapping: %s ==> %s' % (k, v))
                    mappings[k] = v
    
            # 删除这些已经在字典中存储的属性
            for k in mappings.keys():
                attrs.pop(k)
    
            # 将之前的uid/name/email/password以及对应的对象引用、类名字
            attrs['__mappings__'] = mappings  # 保存属性和列的映射关系
            attrs['__table__'] = name  # 假设表名和类名一致
            return type.__new__(cls, name, bases, attrs)
    
    
    # 处理sql的insert语句生成
    class Model(object, metaclass=ModelMetaclass):
    
        def __init__(self, **kwargs):
            for name, value in kwargs.items():
                print('__init__: %s ==> %s' % (name, value))
                # 给属性设置值
                setattr(self, name, value)
    
        def save(self):
            fields = []
            args = []
            for k, v in self.__mappings__.items():
                # 字段取元组中的第一个作为sql的字段
                fields.append(v[0])
                # 字段对应的值通过getattr方法获取
                args.append(getattr(self, k, None))
    
            args_temp = list()
            for temp in args:
                # 判断入如果是数字类型
                if isinstance(temp, int):
                    args_temp.append(str(temp))
                elif isinstance(temp, str):
                    args_temp.append("""'%s'""" % temp)
            sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join(args_temp))
            print('SQL: %s' % sql)
    
    
    # 继承父类Model
    class User(Model):
        uid = ('uid', "int unsigned")
        name = ('username', "varchar(30)")
        email = ('email', "varchar(30)")
        password = ('password', "varchar(30)")
        # 当指定元类之后,以上的类属性将不在类中,而是在__mappings__属性指定的字典中存储
        # 以上User类中有
        # __mappings__ = {
        #     "uid": ('uid', "int unsigned")
        #     "name": ('username', "varchar(30)")
        #     "email": ('email', "varchar(30)")
        #     "password": ('password', "varchar(30)")
        # }
        # __table__ = "User"
    
    
    u = User(uid=12345, name='Michael', email='test@orm.org', password='my-pwd')
    u.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

    运行结果如下:

    Found mapping: uid ==> ('uid', 'int unsigned')
    Found mapping: name ==> ('username', 'varchar(30)')
    Found mapping: email ==> ('email', 'varchar(30)')
    Found mapping: password ==> ('password', 'varchar(30)')
    __init__: uid ==> 12345
    __init__: name ==> Michael
    __init__: email ==> test@orm.org
    __init__: password ==> my-pwd
    SQL: insert into User (uid,username,email,password) values (12345,'Michael','test@orm.org','my-pwd')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
  • 相关阅读:
    springboot毕设项目大学生创新创业项目管理60qsr(java+VUE+Mybatis+Maven+Mysql)
    Java项目:springBoot+Mysql实现的校园二手在线交易平台系统
    应用统计-点估计法(1.矩估计 2.极大似然估计)
    MongoDB安装、基础操作和聚合实例详解
    Mybatis04(关联关系映射)
    一文搞定Spring依赖注入日常使用以及底层原理
    【云原生】FlexCloud可视化操作用户体验
    一图带你领略Spring Bean的生命周期全过程
    gRPC 四模式之 双向流RPC模式
    自动化测试——selenium(环境部署和元素定位)
  • 原文地址:https://blog.csdn.net/mChenys/article/details/133764059