元类、class底层分析、对象生成控制
太难了太难了,顶不住顶不住
元类
前方核能,这可能是我比较装逼的一讲,全部退后,我要开装了。
首先我们要把什么是元类搞清楚。
我们知道的,在python中,万物皆对象,其实在学到元类之前,我真的无法体会,我只知道这么一个概念,知道遇见了元类,我才体会到这个概念的意思。
我们有类,然后通过实例化,来产生这个类的对象,是吧,那有没有想过,类是怎么来的,对没错,是class出来的,那有没有想过它里面发生了什么操作?
其实,类他也是对象,他是元类的对象,又元类实例化出来的对象就是类!现在不要再把对对象的概念局限在只有通过类实例化出来的东西了,万物皆对象,是不是可以感受到那么一点点?
那么元类的概念也清楚了,元类就是实例化类对象的类。那么元类是什么样子的呢?
print(type(dict))
print(type(list))
print(type(str))
print(type(object))
print(type(type))
<class 'type'>
<class 'type'>
<class 'type'>
<class 'type'>
<class 'type'>
我们可以看到这些类的类型全都是 type类型的,就连 type 本身,都是 type 类。
那么基本可以确定了, type ,就是元类。并且所有的类都是由它实例化出来的,然后被实例化出来的类才能实例化出一个个对象。
那么既然元类也是类,第一个元类怎么来的?先有鸡还是先有鸡蛋?第一个元类是用C语言写的,相信小傻子们都明白了凡是遇到解释不清楚的东西,你都可以说是C写的。。。。。
这个就不要去想了,他已经是最底层的东西了。
class底层原理分析
我们知道 class +类名,就会把类给构造出来了,通过上一节我们也知道了,其实并没有我们想的那么简单,实际上是元类实例化产生了类这个对象。
实际上,它是经历了这么一个步骤。
Person=type('Person',(object,),dict)
type的返回值是一个类,Person来接收了这个返回值,于是就有了一个叫做‘Person’的类,赋值给了Person。你就可以用这个Person来实例化各种对象了。
首先我们要讲一下这个各个参数的作用,第一个参数,很明显就是要创建的这个类的名字,第二个就是这个类要继承的父类,一定要写成元组或者列表的形式,因为你不一定只继承一个类,第三个才是关键,是一个字典,那么这个字典里面写的是什么呢?你回想一下你之前创建一个类的时候,是不是要写很多属性,很多方法在里面,这时候这些属性和方法,全都被挤到这个dict里面去了
l={}
exec('''
school='oldboy'
def __init__(self,name):
self.name=name
def score(self):
print('分数是100')
''',{},l)
exec方法,一共三个参数,第一个是字符串,第二个是全局变量存放的字典,第三个是局部变量的存放字典,我们要拿的是类里面的东西,所以我们需要的是第三个参数,然后全放进了 l 里面,这个exec的作用,他只是以字符串的形式把代码块放在了第一个参数的位置,然后执行这些代码,就会生成各种名称空间,有一个名称空间栈和名称空间堆,栈放变量名和方法名,堆放变量值和方法,打印出来为了方便观看,会直接以变量名:变量值的形式,但是方法不是,方法没办法打印出来,只能是方法名:方法地址,然后把这些名称空间的映射关系存在第三个参数 l 里面,可以说 l 里面存的就是映射关系。那么我们来打印一下这个 l。
{'school': 'oldboy', '__init__': <function __init__ at 0x0000016746FD1EA0>, 'score': <function score at 0x00000167471BD048>}
果然是以一个存放映射的字典,然后我们把这个字典扔进type的第三个参数里
Person=type('Person',(object,),l)
就变成了这样了,那么到这里,类就生成好了,我们就可以用这个Person类来实例化对象了。
肯定有人会有这么一个问题,为什么type方三个参数会生成类,放一个参数却是返回类型。
看,他的源码就是这样的。我们用type这个元类实例化一个类的时候,就会调用type的init方法,里面具体是这样的。
通过元类来控制类的产生
我们知道的,元类就是type,已经写好了的,我们动不了,那我们怎么通过元类来控制类的产生呢,我们可以自己写,对自己写,然后把别的类的默认的元类改成自己写的元类就好了。
自定义元类:来控制类的产生:可以控制类名,可以控制类的继承父类,控制类的名称空间
自定义元类必须继承type,写一个类继承type ,这种类都叫元类
class Mymeta(type):
# def __init__(self,*args,**kwargs):
def __init__(self,name,bases,dic):
# self 就是Person类
# print(name)
# print(bases)
# print(dic)
这样我们就写好了一个自定义的元类了。
来做个小练习
练习一:加限制 控制类名必须以sb开头
class Mymeta(type):
# def __init__(self,*args,**kwargs):
def __init__(self,name,bases,dic):
if not name.startswith('sb'):
raise Exception('类名没有以sb开头')
比如我们在实例化一个类
class person(metaclass=Mymeta):
name='nick'
我们在类的括号里面加了一个metaclass=Mymeta,这就是指定了他的元类是我们自定义的那个类,而不是默认的type。这时候我们把name(也就是自身的类名),bases(也就是自身的父类,这里没有),和dic(自身的代码块)传进了自定义元类的init方法里,下面的操作你们就懂了。
练习二:类必须加注释
class Mymeta(type):
# def __init__(self,*args,**kwargs):
def __init__(self,name,bases,dic):
if not self.__dict__['__doc__']:
raise Exception('类里没有注释')
这个就不多解释了,一样的道理
通过元类控制类的调用过程
这个和上面的有什么区别呢?
区别就是这个才是细节
控制类的调用过程,实际上在控制:对象的产生
怎么实现?用__call__来实现
比如说我们必须要新生成的类是以 qssb开头的,就可以在call里面写判断逻辑。
class Mymeta(type):
def __call__(self, *args, **kwargs):
obj=object.__new__(self)
obj.__init__(*args, **kwargs)
# print(obj.__dict__)
if not obj.__class__.__name__.startswith('qssb'):
raise Exception('类名没有以qssb开头')
return obj
class Person(object,metaclass=Mymeta):
school='oldboy'
def __init__(self,name):
self.name=name
def score(self):
print('分数是100')
p=Person('nick')
print(p.name)
我不想解释这个代码了,我累了。
因为你的实例化出的p,会调用Person(),所以就会执行他的元类的__call__方法,在这里面有调用了自身的init,懂了吧。首先我们看一下这个new,在他的括号里面写一个类的名称,就能够实例化出一个空的对象。然后就你懂得了
有了元类之后的属性查找
类的属性查找顺序:先从类本身中找--->mro继承关系去父类中找---->去自己定义的元类中找--->type中--->报错
对象的属性查找顺序:先从对象自身找--->类中找--->mro继承关系去父类中找--->报错
结束结束,腰痛