元类
一、元类的概念
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继承顺序列表去父类中找--》没有就报错