• 元类


    元类

    一、什么是元类

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

    简单说只要继承了type他就是元类

    class Person:  # Foo=元类()
        pass
    

    元类 type—实例化--->类Foo ---实例化类--> 对象

    114-元类metaclass-类的创建.png?x-oss-process=style/watermark

    Person类也是个对象,那他一定是由一个类实例化得到的,这个类就叫元类

    1.1 如何找元类

    print(type(p1))
    
    # 同理:type类是产生所有类的元类
    print(type(Person))
    print(type(list))
    print(type(dict))
    print(type(object))
    
    
    # 内置函数
    print(type(print))
    
    <class '__main__.Person'>
    <class 'type'>
    <class 'type'>
    <class 'type'>
    <class 'type'>
    <class 'builtin_function_or_method'>
    

    二、为什么用元类

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

    三、 内置函数exec

    cmd = """
    x=1
    print('exec函数运行了')
    def func(self):
        pass
    """
    class_dic = {}
    # 执行cmd中的代码,然后把产生的名字丢入class_dic字典中
    exec(cmd, {}, class_dic)
    

    exec函数运行了

    print(class_dic)
    

    {'x': 1, 'func': <function func at 0x10a0bc048>}

    四、class创建类

    • 如果说类也是对象,那么用class关键字的去创建类的过程也是一个实例化的过程,该实例化的目的是为了得到一个类,调用的是元类
    • 用class关键字创建一个类,用的默认的元类type,因此以前说不要用type作为类别判断
    • class 加类名,就会把类构造出来
    class People:  # People=type(...)
        country = 'China'
    
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        def eat(self):
            print('%s is eating' % self.name)
    
    print(type(People))
    

    <class 'type'>

    六、type元类

    Person 类是有type实例化产生的,向type传了一堆参数,然后type() 调用类的__init__方法,就会创建一个类,

    type()创建类格式:

    type(object, bases(继承的基类), dict)

    object: object (所有的基类) or name (类名:你所要创建的名字),类的名字, 是个字符串;

    base: 是他的所有的父类(元组的形式)、基类;
    dict: 名称空间,是一个字典;

    创建类的3个要素:类名,基类,类的名称空间 ;

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

    l = {}
    g = {}
    exec("""
    school='hnnu'
    def __init__(self,name):
        self.name=name
    def score(self):
        print('分数是100')
    """, g, l)
    
    def __init__(self, name):
        self.name = name
    
    
    # 创建类 People = type(类名,基类,类的名称空间)
    Person = type('Person', (object, ), {'school': 'hnnu', '__init__': __init__})
    
    # 创建对象
    p = Person('randy')
    print(p.name)
    
    # 创建类
    Person = type('Person', (object,), l)
    print("*"*9)
    print(Person)
    print("*"*9)
    
    print()
    
    type('Person', (object,), l)
    # # 获取属性值
    print(Person.school)
    #
    print(Person.__dict__)
    #
    print(Person.__bases__)
    print(Person.mro())
    

    七、通过元类来控制类的产生

    自定义元类:来控制类的产生, 可以控制类名, 可以控制类的继承父类, 控制类的名称空间
    自定制的元类必须继承type,写一个类继承type,这种类就叫元类

    class Mymeta(type):
    
        # def __init__(self, *args, **kwargs):
    
        def __init__(self, name, base, dic):
            print(name) # 类名
            print(base) # 基类
            print(dic) # 名称空间
    
    """
    通过 class创建的类继承,自定义的元类, 在Person中定义第一个参数表示类的名称,第二个参数表示继承的基类
    ,而类中的代码则是,产生的名称空间,
    """
    class Person(object, metaclass=Mymeta):
        school = 'hnnu'
    
        def __init(self, name):
            self.name = name
    
        def score(self):
            print("分数是: 100")
    
    
    p = Person()
    
    

    7.1 控制类的产生

    # 练习一类名以sb开头
    class Mymeta(type):
    
        def __init__(self, name, base, dic):
            # 练习一 加限制, 控制类名必须以sb开头
            print(name)
            if not name.startswith('sb'):
                raise Exception("类名没有以sb开头")
    
    
    class sb_Person(object, metaclass=Mymeta):
        school = 'hnnu'
    
        def __init__(self, name):
            self.name = name
            print(2)
    
        def score(self):
            print("分数是: 100")
    
    
    p1 = sb_Person('ran')
    
    
    # 练习二, 类必须有注释
    class Mymeta(type):
    
        def __init__(self, name, base, dic):
            print(self.__dict__)  # 产生的名称空间是类
            print(dic)
            doc = self.__dict__['__doc__']
            print("元类")
            if not doc:
                raise Exception("必须有注释")
    
            print(doc)
    
    
    class Person(object, metaclass=Mymeta):
        """
        我已经注释了
        """
        school = 'hnnu'
    
        def __init__(self, name):
            print("自己")
            self.name = name
    
        def score(self):
            print("分数是: 100")
    
    
    # 继承元类,首先会先进入元类中的__init__在返回来执行自己的
    p1 = Person('ran')
    

    继承元类,首先会先进入元类中的__init__在返回来执行自己的

    7.2 控制类产生的模板

    #  控制类产生模板
    class Mymeta(type):
        def __init__(self, name, base, dic):
            if self.name == Person:
                raise Exception("名称错误")
    
    
    class Person(metaclass=Mymeta):
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
    
    p = Person('laowang', 19)
    
    

    八、通过元类控制类的调用过程(控制创建类对象的过程)

    要想让obj这个对象变成一个可调用的对象,需要在该对象的类中定义一个方法、__call__方法,该方法会在调用对象时自动触发

    我们之前说类实例化第一个调用的是__init__,但__init__其实不是实例化一个类的时候第一个被调用 的方法。当使用 Persion(name, age) 这样的表达式来实例化一个类时,最先被调用的方法 其实是 __new__ 方法。

    __new__方法接受的参数虽然也是和__init__一样,但__init__是在类实例创建之后调用,而 __new__方法正是创建这个类实例的方法。

    8.1 __call__控制类的调用过程

    # 创建元类
    class Mymeta(type):
        def __call__(self, *args, **kwargs):
            print(self)
            print(self.__dict__)
            print(args)
            print(kwargs)
            print('进入__call__')
            return 123
    
    # 创建类
    class Person(object, metaclass=Mymeta):
        school = 'hnnu'
    
        def __init__(self, name):
            print('person__init__')
            self.name = name
    
        def score(self):
            print("分数100")
    
        def __call__(self, *args, **kwargs):
            print('Person 进入__call__')
            return 1
    
    <class '__main__.Person'>
    ('randy',)
    {}
    进入元类__call__
    123
    

    8.2 __call__控制类的调用过程实现

    class Mymeta(type):
       
        def __call__(self, *args, **kwargs):
    
            """
            因为__call__返回的空对象,所以要自己生成对象,通过__new方式)
            :param self: self ==> Person
            :param args: Person(randy)
            :param kwargs: Person(age=18)
            :return:
            """
            print('进入元类__call__')
    
            # self ==> Person
    
            # 出现递归的原因 self()加括号运行相当于Person()所以还是调用元类的__call__方法
            # return self(*args) # 出现递归
    
            # 实例化产生一个Person类的对象,借助__new__来产生,需要把类传过去,才能产生对象
            # 方式一(调用父类object中的__new__)
            self.__new__(self)
            # obj 是Person类的对象,只不过是空的,
            # 方式二
            obj = object.__new__(self)  # (因为__call返回的空对象,所以要自己生成对象,通过__new方式)
    
            # obj = self.__new__(self)
    
            # 调用__init__方法完成初始化
            # 类来调用__init__方法, 就是个普通函数,有几个参数就要传几个参数
            # self.__init__(obj, *args, **kwargs)
    
            # 对象来调用__init__方法,对象的绑定方法,会把自身传过去
    		
            kwargs['age'] = 18
            
            obj.__init__(*args, **kwargs)
    
            print(obj)
            return obj
    
    
    class Person(object, metaclass=Mymeta):
        school = 'hnnu'
    
        def __init__(self, name, *args,**kwargs):
            print('person__init__')
            self.name = name
    
        def score(self):
            print("分数100")
    
        def __call__(self, *args, **kwargs):
            print('Person 进入__call__')
            return 1
    
    
    p = Person('randy')
    print(p.__dict__)
    print(Person.__dict__)
    p.name
    
    进入元类__call__
    person__init__ 2 
    {'age': 18}
    <__main__.Person object at 0x038D4D70>
    {'name': 'randy'}
    {'__module__': '__main__', 'school': 'hnnu', '__init__': <function Person.__init__ at 0x0C68FF18>, 'score': <function Person.score at 0x0C68FED0>, '__call__': <function Person.__call__ at 0x0C68FE88>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None}
    
    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 = 33  # 39;18&#39;
        def __init__(self, name):  # 当类实例化生成对象后,会通过call调用init方法
            print(1)
            self.name = name
    
    
    p = Person(name= 'randy') # 先调用自定义元类的call方法
    print(p.__dict__)
    print(p.name)
    print(p)
    
    3
    ('Person', (), {'__module__': '__main__', '__qualname__': 'Person', 'age': 33, '__init__': <function Person.__init__ at 0x0C017198>})
    2
    [<class '__main__.Person'>, <class 'object'>]
    1
    {'name': 'randy'}
    randy
    <__main__.Person object at 0x03263730>
    

    总结:

    1. __call__的第一个参数self是创建类的类名,第二、三参数为创建对象属性的值;
    2. 直接返回return self(*args) 会出现递归原因在于,self(*args)相当于Person(*args)最终还是调用元类的 __call__,所以会出现死循环;解决办法,创建类对象调用自己的__init__方法
    # 方式一
    obj = object.__new__(self)  # (因为__call返回的空对象,所以要自己生成对象,通过__new方式)
    # obj = object.__new__(Person)
    
    obj.__init__(*args, **kwargs) # 调用Person类中自己的__init__方法
    return obj
    
    # 方式三
    obj =  self.__new__(self) # 调用还是object中的__new__方法
    obj.__init__(*args, **kwargs) #等同与 self.__init__(obj, *args, **kwargs) 调用Person类中自己的__init__方法
    return obj
    
    
    print(self.__new__ is object.__new__) #True
    
    

    8.3 当前对象的__call__

    class Person1():
        school = 'oldboy'
    
        def __init__(self, name):
            print(1)
            self.name = name
    
        def score(self):
            print('分数是100')
    
        def __call__(self, *args, **kwargs):
            print('xxxx')
    
    # 自动调用触发init的执行
    p = Person1('randu')
    
    # 触发自己的__call__
    p()
    
    

    print('xxxx')

    8.4 __call__ 最终版

    注意:__new()__ 函数只能用于从object继承的新式类。

    class Mymeta(type):
        def __call__(self, *args, **kwargs):
            print(self)  # self是People
            print(args)  # args = ('nick',)
            print(kwargs)  # kwargs = {'age':18}
            # return 123
            # 1. 先造出一个People的空对象,申请内存空间
            # __new__方法接受的参数虽然也是和__init__一样,但__init__是在类实例创建之后调用,而 __new__方法正是创建这个类实例的方法。
            obj = self.__new__(self)  # 虽然和下面同样是People,但是People没有,找到的__new__是父类的
            # 2. 为该对空对象初始化独有的属性
            self.__init__(obj, *args, **kwargs)
            # 3. 返回一个初始化好的对象
            return obj
    
    • People = Mymeta(),People()则会触发__call__
    class People(object, metaclass=Mymeta):
        country = 'China'
    
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        def eat(self):
            print('%s is eating' % self.name)
    
    
    #     在调用Mymeta的__call__的时候,首先会找自己(如下函数)的,自己的没有才会找父类的
    #     def __new__(cls, *args, **kwargs):
    #         # print(cls)  # cls是People
    #         # cls.__new__(cls) # 错误,无限死循环,自己找自己的,会无限递归
    #         obj = super(People, cls).__new__(cls)  # 使用父类的,则是去父类中找__new__
    #         return obj
    
    • 类的调用,即类实例化就是元类的调用过程,可以通过元类Mymeta的__call__方法控制

    • 分析:调用Pepole的目的

      1. 先造出一个People的空对象
      2. 为该对空对象初始化独有的属性
      3. 返回一个初始化好的对象
      obj = People('randy', age=18)
      

      <class '__main__.People'>
      ('randy',)
      {'age': 18}

      print(obj.__dict__)
      

      {'name': 'randy', 'age': 18}

    九、把对象属性都变成私有

    class Mymeta(type):
        def __call__(self, *args, **kwargs):
    
            print("__call__ 第一")
            # 产生空对象
            obj = object.__new__(self)
            # 调用子类__init__
            obj.__init__(*args, **kwargs)
    		
            # 名称空间创建完成之后进行处理
            # 会进入self==> person类中__init__然后在回来
            obj.__dict__ = {f"_{self.__name__}__{k}": v for k, v in obj.__dict__.items()}
    
            print("123456", obj.__dict__)
            print("__call__ ", obj.__dict__)
    
            return obj
    
    
    class Person(object, metaclass=Mymeta):
        def __init__(self, name):
            print("___init__ 第二")
            self.name = name
    
    
        def score(self):
            print('分数是100')
    
    
    p = Person(name='randy')
    print(p.__dict__)
    print(p._Person__name)
    
    
    p = Person(name='randy')
    print(p.__dict__)
    print(p._Person__name)
    
    __call__ 第一
    ___init__ 第二
    123456 {'_Person__name': 'randy'}
    __call__  {'_Person__name': 'randy'}
    {'_Person__name': 'randy'}
    randy
    

    十、自定义元类后继承顺序

    结合python继承的实现原理+元类重新看属性的查找应该是什么样子呢???

    在学习完元类后,其实我们用class自定义的类也全都是对象(包括object类本身也是元类type的 一个实例,可以用type(object)查看),我们学习过继承的实现原理,如果把类当成对象去看,将下述继承应该说成是:对象OldboyTeacher继承对象Foo,对象Foo继承对象Bar,对象Bar继承对象object

    class Mymeta(type):  # 只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
        n = 444
    
        def __call__(self, *args,
                     **kwargs):  #self=<class '__main__.OldboyTeacher'>
            obj = self.__new__(self)
            self.__init__(obj, *args, **kwargs)
            return obj
    
    
    class Bar(object):
        n = 333
    
    
    class Foo(Bar):
        n = 222
    
    
    class OldboyTeacher(Foo, metaclass=Mymeta):
        n = 111
    
        school = 'oldboy'
    
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        def say(self):
            print('%s says welcome to the oldboy to learn Python' % self.name)
    
    
    print(
        OldboyTeacher.n
    )  # 自下而上依次注释各个类中的n=xxx,然后重新运行程序,发现n的查找顺序为OldboyTeacher->Foo->Bar->object->Mymeta->type
    

    111

    print(OldboyTeacher.n)
    

    111

    • 查找顺序:
      1. 先对象层:OldoyTeacher->Foo->Bar->object
      2. 然后元类层:Mymeta->type

    依据上述总结,我们来分析下元类Mymeta中__call__里的self.__new__的查找

    总结:

    1. 类的属性查找顺序:先从类本身中找--->mro继承关系去父类中找---->去自己定义的元类中找--->type中--->报错
    2. 对象的属性查找顺序:先从对象自身找--->类中找--->mro继承关系去父类中找--->报错

    10.1 练习

    class Mymeta(type):
        def __init__(self, class_name, class_bases, class_dic):
            # 加上逻辑,控制类Foo的创建
            super(Mymeta, self).__init__(class_name, class_bases, class_dic)
    
        def __call__(self, *args, **kwargs):
            # 加上逻辑,控制Foo的调用过程,即Foo对象的产生过程
            obj = self.__new__(self)
            self.__init__(obj, *args, **kwargs)
            # 修改属性为隐藏属性
            obj.__dict__ = {
                '_%s__%s' % (self.__name__, k): v
                for k, v in obj.__dict__.items()
            }
    
            return obj
    class Foo(object, metaclass=Mymeta):  # Foo = Mymeta(...)
        def __init__(self, name, age, sex):
            self.name = name
            self.age = age
            self.sex = sex
    
    
    obj = Foo('randy', 18, 'male')
    

    print(obj.__dict__)

    十一、总结

    继承元类,首先会先进入元类中的__init__在返回来执行自己的

    控制类产生模板

    #  控制类产生模板
    class Mymeta(type):
        def __init__(self, name, base, dic):
            if self.name == Person:
                raise Exception("名称错误")
    
    
    class Person(metaclass=Mymeta):
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
    
    p = Person('laowang', 19)
    
    

    控制对象的产生

    class Mymeta(type):
        def __call__(self, *args, **kwargs):
    
            # 第一步产生空对象
    
            obj=object.__new__(self)
            # 第二部初始化空对象,把初始值放到对象中
            obj.__init__(*args, **kwargs)
            # 第三步返回对象
            return obj
    
    class Person(metaclass=Mymeta):
        def __init__(self,name):
            self.name=name
        def __call__(self, *args, **kwargs):
            print('xxx')
    
    p=Person('randy')
    

    对象查找顺序

    类的属性查找顺序:先从类本身中找--->mro继承关系去父类中找---->去自己定义的元类中找--->type中--->报错

    对象的属性查找顺序:先从对象自身找--->类中找--->mro继承关系去父类中找--->报错

    在当下的阶段,必将由程序员来主导,甚至比以往更甚。
  • 相关阅读:
    【转】DOS命令大全(远程命令)
    system CPU占用率过高与91助手的关系
    要像管理咨询一样去做软件需求调研
    近两个月工作日志
    ECSHOP:首页实现显示子分类商品,并实现点击Tab页切换分类商品
    奋战5个小时解决诡异的PHP“图像XX因其本身有错无法显示”的问题
    SVN强制添加日志出现E205000错误解决方法
    pdf文件之itextpdf操作实例
    验证码实例
    Struts2拦截器记录系统操作日志
  • 原文地址:https://www.cnblogs.com/randysun/p/11455526.html
Copyright © 2020-2023  润新知