zoukankan      html  css  js  c++  java
  • 元类(metaclass)

    • 元类属于python面向对象编程的深层魔法,99%的人都不得要领,一些自以为搞明白元类的人其实也只是自圆其说、点到为止,从对元类的控制上来看就破绽百出、逻辑混乱,今天我就来带大家来深度了解python元类的来龙去脉。
    • 笔者深入浅出的背后是对技术一日复一日的执念,希望可以大家可以尊重原创,为大家能因此文而解开对元类所有的疑惑而感到开心!!!

    一、什么是元类

    • 在python中一切皆对象,那么我们用class关键字定义的类本身也是一个对象,负责产生该对象的类称之为元类,即元类可以简称为类的类

    • 元类是负责产生类的,所以我们学习元类或者自定义元类的目的:是为了控制类的产生过程,还可以控制对象的产生过程

    • type是内置的一个元类,所有的类都是由type实例化得到的,产生类的类,叫元类

    • type的实例就是type,并且它还继承了object类,而object类也是type类的实例。

    class Person:
        def __init__(self,name):
            self.name = name
    
    p = Person("xc")
    
    # 使用type查看 对象 所属的类
    print(type(p))
    
    print(type(Person))
    print(type(dict))
    print(type(str))
    print(type(list))
    print(type(object))
    print(type(type))
    

    <class 'main.Person'>
    <class 'type'>
    <class 'type'>
    <class 'type'>
    <class 'type'>
    <class 'type'>
    <class 'type'>

    由此可知:type类是产生所有类的元类

    1.1 元类的继承关系图

    元类的继承关系图

    二、 class底层原理分析

    • class 类名 会把类构造出来(类也是个对象)
    • 实际上是:class 底层就是调用type来实例化产生类(对象),需要传一堆参数。只不过class关键字帮我们干了这个事情
    • type(object_or_name, bases, dict)

    object_or_name:类的名字,是个字符串
    bases:是它的所有父类,基类
    dict:名称空间,是一个字典

    2.1 通过type直接产生类,不用class关键字

    # 自己手动去创建
    def __init__(self,name):
        self.name=name
    Person = type('Person',(object,),{'age':'18','__init__':__init__})
    print(Person.__dict__)
    

    {'age': '18', 'init': <function init at 0x0000029BF58D28C8>, 'module': 'main', 'dict': <attribute 'dict' of 'Person' objects>, 'weakref': <attribute 'weakref' of 'Person' objects>, 'doc': None}

    2.2 通过exec内置函数和type创建类,不用class关键字

    l={}
    exec('''
    age='18'
    def __init__(self,name):
        self.name=name
    def score(self):
        print('分数是100')
    ''',{},l)   # 三个参数:要执行的内容,全局名称空间是个字典,局部名称空间(执行的结果会放到这里)
    Person=type('Person',(object,),l)
    print(Person.__dict__)  # 类中属性
    p=Person('xc')  # 实例化对象
    print(p.name)   # 查看对象属性
    print(p.__dict__)   # 对象中属性
    

    {'age': '18', 'init': <function init at 0x000002853A9DC1E0>, 'score': <function score at 0x000002853C81DEA0>, 'module': 'main', 'dict': <attribute 'dict' of 'Person' objects>, 'weakref': <attribute 'weakref' of 'Person' objects>, 'doc': None}
    xc
    {'name': 'xc'}

    三、 自定义元类来控制类的产生

    自定义元类控制类的产生:可以控制类名,可以控制类的继承父类,控制类的名称空间

    自定义元类必须继承type,如果一个类继承type, 这种类都叫元类

    先写元类,在通过元类去控制类的产生,要在类继承的括号中写上metaclass=元类名表示使用的元类

    class Mymeta(type):
        # def __init__(self,*args,**kwargs):	# 正常写法,但是我们已经知道type传递的三个参数的意义
        def __init__(self,name,bases,dic):
            # self 就是Person类
            print(name)		# 创建的类名
            print(bases)	# 继承的所有父类
            print(dic)		# 类中属性,是一个字典
            
            #加限制 控制类名:必须以sb开头
            if not name.startswith('sb'):
                raise Exception('类名没有以sb开头')
    
            #类必须加注释
            print(self.__dict__['__doc__'])
            doc=self.__dict__['__doc__']
            if not doc:
                #没有加注释
                raise Exception('你的类没有加注释')
    #metaclass=Mymeta  指定这个类生成的时候,用自己写的Mymeta这个元类
    class Person(object,metaclass=Mymeta):
    	age='18'
        def __init__(self,name):
            self.name=name
        def score(self):
            print('分数是100')
    p=Person("xc")
    

    Person

    (<class 'object'>,)

    {'module': 'main', 'qualname': 'Person', 'doc': ' 注释 ', 'age': '18', 'init': <function Person.init at 0x000001B97FDF19D8>, 'score': <function Person.score at 0x000001B97FDF1A60>}

    Exception: 类名没有以sb开头

    四、通过自定义元类控制类的调用过程

    控制类的调用过程,实际上在控制:对象的产生 __call__

    之前的内容说过,__call__当对象加括号就会调用它。那么现在放到类对象也同样适用。

    但只是说过类名()实例化会先去调用init绑定方法,现在又说call是不是很混乱。

    实际上,类名()实例化是先去调用call方法,再去调用init方法

    如:

    class Mymeta(type):
        def __call__(self, *args, **kwargs):
            print('xxx')
            return 1
    
    class Person(object,metaclass=Mymeta):
        age='18'
        def __init__(self,name):
            self.name=name
        def score(self):
            print('分数是100')
            
    #先触发元类的__call__,再触发init的执行
    p=Person('nick')    # 当类对象加括号实际上就是去调用了自定义元类中的__call__方法
    print(p)   # 所以p等于1
    # print(p.name)   # 没有返回对象,p等于1所以一定会报错
    

    那么,如果像上面那样,不就得不到实例化对象了嘛?

    实际上,实例化创建对象是通过call方法中的new方法。这些call方法,new方法,init都是一些魔法方法

    但是new方法只会得到一个新的对象什么都没有的对象

    但我们实例化得到对象的本事是为了共享类中属性和方法,需要绑定上属性和方法。这时候就需要再使用init方法

    如:

    class Mymeta(type):
        def __call__(self, *args, **kwargs):
            # self 就是Person类
            print(2)
            print(self.mro()) # 类对象的属性查找顺序
            # obj=self.__new__(self)  # 方法1,通过self的new方法创建对象
            obj=object.__new__(self)  # 方法2,通过object的new方法创建对象
            obj.__init__(*args, **kwargs)   # 调用对象的init方法
            return obj
    
        def __init__(self,*args):
            print(3)
            print(args)
            
    class Person(metaclass=Mymeta): # 实际上是先调用元类的call方法,再回调自定义元类的init方法
        age = '18'
        def __init__(self, name):   # 当类实例化生成对象后,会通过call调用init方法
            print(1)
            self.name = name
    p = Person(name='xc') # 先调用自定义元类的call方法
    print(p.__dict__)
    print(p.name)
    print(p)
    

    3
    ('Person', (), {'module': 'main', 'qualname': 'Person', 'age': '18', 'init': <function Person.init at 0x000001E8A59D1AE8>})
    2
    [<class 'main.Person'>, <class 'object'>]
    1
    {'name': 'xc'}
    xc
    <main.Person object at 0x000001E896A80CC0>

    仔细阅读代码,根据执行结果查看调用顺序

    五、有了元类之后的属性查找

    • 类的属性查找顺序:先从类本身中找--->mro继承关系去父类中找---->去自己定义的元类中找--->type中--->报错
    • 对象的属性查找顺序:先从对象自身找--->类中找--->mro继承关系去父类中找--->报错
  • 相关阅读:
    TCP的三次握手与四次挥手
    关系型数据库和非关系型数据库的区别
    wedpack打包的基本使用
    express的中间件与next()
    react-redux (react)
    判断数据类型的几种方式
    关于NODE__APP在windows系统解决适配问题
    中间件,前后端分离思想
    移动端
    EasyUI combobox 动态下拉列表
  • 原文地址:https://www.cnblogs.com/XuChengNotes/p/11454333.html
Copyright © 2011-2022 走看看