元类

元类

一、元类的概念

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

原文地址:https://www.cnblogs.com/zhuangyl23/p/11456195.html