zoukankan      html  css  js  c++  java
  • 元类

    元类

    一、元类的概念

    python中一切皆对象,其实类就是一个一个对象。我们之前定义过Person的类也是对象,它就是由一个类实例化得到的,这个类就是元类。

    • 元类:能够实例化产生类的类

      目前的元类type是一个内置的元类,所有的类都是由type实例化得到的

    • 如何找元类

      print(type(类名))  # 这样可以打印出类的元类
      
    • 元类的作用:

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

    二、class底层原理

    class 类名 就会把类构造出来,实际上是元类实例化产生了类这个对象。类实例化产生对象,一定是:类名() 这个方式。

    class 底层就是调用元类type来实例化产生类(对象)

    类是由type实例化产生,需要传一堆参数。type() 就会调用类的__init__方法。

    元类type()实例化产生类进行传参:type(object_or_name,bases,dict)

    参数中object_or_name:传入的值是类的名字,是个字符串

    ​ bases:传入的值是要产生的类的所有父类、基类 ,以元组的形式

    ​ dict:是类的名称空间,是一个字典,传入的值也是字典,里面是类的属性和方法的key:value格式

    例如元类type实例化产生一个类Person:

    Person=type('Person',(object,),{'school':'oldboy','__init__':__init__})
    

    exec内置函数是用来写产生的类中的属性和方法的,执行字符串的代码

    exec(字符串,{},l) 字符串可以写属性和方法,{}是全局名称空间字典,l是局部名称空间字典,执行字符串内的代码,在字符串内定义的属性和方法会放到局部的名称空间字典l内

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

    l = {}   
    exec('''
    school = 'oldboy'
    def __init__(self,name):
        self.name = name
    def score(self):
        print('分数是100')
    ''',{},l)   # 只用到局部名称空间,因此全局的就是空字典,把字符串内容都放到l内
    
    Person = type('Person',(object,),l)  # 这就产生了类Person
    
    print(Person.__dict__) # 打印产生的类Person的属性和方法
    print(Person.__bases__) # 打印类Person的父类
    p = Person('nick') # Person()实例化产生对象p会自动调用__init__方法,需要传入一个参数给name
    print(p.name)  # nick
    print(p.__dict__)  # 打印对象p的属性和方法
    

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

    3.1 自定义元类和类的产生

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

    自定义元类必须继承type,也就是写一个类继承type,这种类叫做元类。

    可以通过自定义元类,重写__init__方法来控制类的产生

    这里会用到一个metaclass=Mymeta,意思是指定生成类的时候,用自定义的Mymeta这个元类。

    class Mymeta(type):
        def __init__(self,*args,**kwargs):
            print(args)
            print(kwargs)
    class Person(metaclass=Mymeta):  # 生成类会调用元类的__init__方法 # 这里就相当于Person=Mymeta('Person',(object,),{名称空间}),Mymeta实例化,会把三个参数传到Mymeta的__init__方法中
        school = 'oldboy'
        def __init__(self,name):
            self.name=name
        def score(self):
            print('分数是100')
    

    3.2 自定义元类中加限制语句控制类的产生

    • 例一:控制生成的类名必须以sb开头,用到raise主动抛错
    class Mymeta(type):
        # def __init__(self,*args,**kwargs):
        #     print(args)
        #     print(kwargs)
          def __init__(self,name,bases,dic):
            # self 就是生成的Person类
            # 在这个位置可以加限制,来控制由这个元类产生的类
            # 生成的类名没有以sb开头会执行下面两句代码中的raise
            if not name.startswith('sb'):
                raise Exception('类名没有以sb开头')
    class Person(metaclass=Mymeta):   # 指定元类为自定义的元类Mymeta
        # 生成这个类会调用元类的__init__方法 ,Person类名不是以sb开头,所以会执行元类__init__中的主动抛错
        school = 'oldboy'
        def __init__(self,name):
            self.name = name
        def score(self):
            print('分数是100')
            
    
    • 例二:限制自定义元类产生的类必须加注释
    class Mymeta(type):
        def __init__(self,name,bases,dic):
            # 用到__doc__内置属性来取类的文档字符串
            print(self.__dict__['__doc__'])
            doc=self.__dict__['__doc__']
            if not doc:
                #没有加注释
                raise Exception('你的类没有加注释')
    class Person(object,metaclass=Mymeta):
        # 加了注释就会执行元类__init__中的print,不执行主动抛错那句
        '''
        我加了注释
        '''
        school='oldboy'
        def __init__(self,name):
            self.name=name
        def score(self):
            print('分数是100')
    

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

    控制类的调用过程实际上就是在控制对象的产生

    4.1 控制对象的产生的模板

    class Mymeta(type):
        def __call__(self,*args,**kwargs):
            # self就是生成的类
            # 第一步:生成一个空对象
            obj=self.__new__(self)
            # 第二部:调用__init__初始化对象
            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('zyl')  # 这里会先调用元类的__call__方法,再在元类的__call__方法中调用Person的__init__方法,来完成对象的初始化
    
    

    4.2 __call__ 内置方法

    __call__是对象加括号就会调用这个方法,之前只是定义一个类,自动调用的__call__方法是元类中的,因此我们自定义元类的时候就要在自定义元类中定义__call__方法,该方法必须返回一个对象(由自定义的元类产生的类的对象)。

    class Mymeta(type): #只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
        def __call__(self, *args, **kwargs):
            # 该方法必须返回一个对象(类对象),这个地方返回什么,下面的类()产生的对象就是什么
            print(self) #<class '__main__.OldboyTeacher'>
            print(args) #('egon', 18)
            print(kwargs) #{}
            return 123
    
    class OldboyTeacher(object,metaclass=Mymeta):
        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)
    
    # 调用OldboyTeacher就是在调用OldboyTeacher元类中的__call__方法
    # 然后将OldboyTeacher传给self,溢出的位置参数传给*,溢出的关键字参数传给**
    # 调用OldboyTeacher的返回值就是调用__call__的返回值
    t1=OldboyTeacher('egon',18)  # 这个位置会调用元类的__call__方法,所以在__call__方法中调用了OldboyTeacher的__init__方法
    print(t1) #123
    

    从以上例子分析,实例化对象t1会发生三件事:

    1、产生一个空对象obj

    2、调用__init__方法初始化对象obj

    3、返回初始化好的obj

    相应的,元类中的__call__方法也应该做这三件事

    class Mymeta(type):
        def __call__(self, *args, **kwargs):
            # 返回一个真正的OldboyTeacher类的对象
            #第一步:调用__new__方法产生一个空对象obj
            obj = self.__new__(self) # 此处的self就是要产生的类,必须传参,代表创建一个要产生的类的空对象obj,这句话跟obj=objcet.__new__(self)一样
            #第二部:调用__init__方法初始化对象obj,把初始值放到对象中
            # obj.__init__也就是调用自己的绑定方法,就是产生该对象的类的__init__方法
            # self.__init__(obj,*args,**kwargs) # 类来调用
            obj.__init__(*args,**kwargs) # 对象类调用,与上一行作用一样
            #3、返回初始化好的对象obj
            return obj
    
    class OldboyTeacher(object,metaclass=Mymeta):
        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)
    
    t1=OldboyTeacher('egon',18)
    print(t1.__dict__) #{'name': 'egon', 'age': 18}
    

    以上例子中的__call__方法相当于一个模板,可以在这个基础上改写它的逻辑从而控制调用类OldboyTeacher的过程,例如定义一个元类,产生一个类Person,将Person的对象中的所有属性设置成私有的。

    4.3 举例应用,把对象属性设置成私有的

    
    class Mymeta(type):
        def __call__(self, *args, **kwargs):
            # 调用__new__方法生成一个空对象
            obj = self.__new__(self)
            # 调用__init__方法完成初始化
            obj.__init__(*args,**kwargs)
            # 用字典生成式修改名称空间中的key值得格式,给key值加上__就变成私有
            obj.__dict__ = {'_%s__%s'%(self.__name__,k):v for k,v in obj.__dict__.items()}
            return obj
    
    class Person(object,metaclass=Mymeta):
        school = 'oldboy'
        def __init__(self,name):
            self.name = name
        def score(self):
            print('分数是100')
    p = Person(name = 'nick')
    print(p.__dict__)      # {'_Person__name': 'nick'}
    print(p._Person__name)  # nick
    print(p.name)  # 因为属性变成了私有,所以.name取不到,会报错
    

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

    有了元类之后:

    • 类的属性查找顺序:先从类本身中找--》mro继承顺序列表去父类中找--》去自定义元类中找--》元类type中找--》没有就报错
    • 对象的属性查找顺序:先从对象自身找--》类中找--》按mro继承顺序列表去父类中找--》没有就报错

    img

  • 相关阅读:
    木马后门入侵与RKHunter,ClamAV检测工具
    Jenkins环境搭建
    Mha-Atlas-MySQL高可用
    JAVA企业级应用服务器之TOMCAT实战
    Keepalived高可用集群
    scp ssh-key连接原理
    jumpserver跳板机搭建
    DNS域名解析服务器
    DHCP服务
    Keepalived高可用集群
  • 原文地址:https://www.cnblogs.com/zhuangyl23/p/11456195.html
Copyright © 2011-2022 走看看