元类
元类
一:准备工作之exec()
此内置函数将字符串代码拿过来执行,将执行的结果放入不同的ns中。
exec(code,{}{})
第一个参数是字符串代码,第二个参数是全局ns,第三个参数是局部ns,
只有显示声明为global的变量被放入全局ns,其他都放入局部ns中
像类体里面的都是放入类下的ns中。而全局ns中则会放入默认加载的内置对象。
当然exec()只是为了帮助理解元类,实际中使用到的几率几乎为0。
二:元类
python中一切皆对象
class Person(object,metaclass=type):
pass
所有类的默认基类object,默认元类是type,因此都没有显示呈现。
元类是用来创建类对象的类,是在后面默默帮助我们做了许多事的类,它负责在语法分析和词法分析时创建类对象。
以便我们可以使用类对象创建实例对象。
例如这里的Person类对象是通过,Person=type(...)创建得到的,只不过不是我们手动调用的而是解释器自动帮助我们调用的。
因此可以得到结论:元类是在文件预读阶段,解释器自动帮助我们创建类对象的工具。
元类实例化----->类对象 类对象实例化------->得到实例对象
三:type函数的参数
Person = type(class_name,class_bases,class_dict)
第一个参数是类名:例如就叫“Person”
第二个参数是所有基类组成的元组:例如默认(object,)
第三个参数是一个字典,因为ns的存在形式就是dict:例如dict(name="zhangsan",age=13)通过执行类体得到的ns,即用exec执行类体字符串得到ns(dict)
元类控制类的行为。
类的行为是什么呢?
就是双下方法在适当的时候触发时被调用的过程。
__init__():在类实例化的时候被调用。
__str__():在打印对象时触发。
__del__():删除对象时触发。
__call__():调用对象时触发,例如people = Person() poople()此时触发__call__()双下方法。
四:元类中的__call__()
上面讲到__call__()在对象调用时被触发,那么在元类里面定义此方法后,类对象调用的时候就会触发type类中的__call__方法
类()触发了__call__(),而Person()不就是实例化吗?
因此实例化对象的本质就是调用元类的__call__()
几乎所有的类的元类都是type,除非你自定义了元类,因此他们都是同样的逻辑
下面来看看元类中__call__()都做了什么事:
class MyType(type):
def __init__(self, class_name,class_father,class_dic):
super().__init__(class_name,class_father,class_dic)
def __call__(self,*args,**kwargs):
#1:创建空对象,第一个参数应该是类对象,这里写Pe会写死,但是传递过来的就是Pe对象,所以用self
#这就是为什么__new__第一个参数为什么是cls
obj1 = object.__new__(self)#这里的self指的是类对象
#2:初始化对象
self.__init__(obj1,*args,**kwargs)#在元类中self始终代表类对象。在类中self代表的对象本身。
#中间的空对象obj1是个临时值,最后返回到全局由你自定义的变量名接收
#3:返回初始化之后的类对象
return obj1
第一个参数self,就和平时类定义里面的self是一样的,只不过这里是类对象,后者为实例对象。
1:创建空对象
obj1=object.__new__(self)#创建什么类就传入什么类对象
2:空对象初始化
元类也是类,因此也有__init__方法
self.__init__(*args,**kwargs)
3:返回对象
return object1
上面所有操作都是解释器预读阶段帮我们创建类对象时自动帮我们完成的。
类对象解释器帮我们创建,实例对象我们自己创建。
此时如果你想控制类创建的过程,就要自定义元类,然后在要控制的类中加metaclass=自定义元类
因此我们实例化对象的本质就是去调用了元类的__call__(),执行的都是上面的三步骤。
当然我们很少去关注和控制类创建的过程,因此很少自定义元类,只是有需要的时候才会那样做,例如必须有文档字符串,类中方法名必须小写等等需求的时候可以找到插入点来控制。
五:类中的__call__()
class Animal():
def __init__(self,*args,**kwargs):
pass
def __call__(self,*args,**kwargs):
pass
类中的__call__()是类创建的对象调用时才会触发
dog = Animal()#触发的是type类中的__call__()
dog()#触发的是Animal类中的__call__()
如果Animal类中没有__call__()方法,那么就会报错,因为基类object里面也没有__call__方法。
六:总结
类的调用-------产生对象,触发元类的__call__(),三步走,创建空对象,调用类的__init__(),返回初始化之后的对象,是在创建对象之中为属性赋值。
dog.age=3这是创建之后进行的赋值,是个性化的操作,没有通用性。
这三步里面,只有第二步是你个性化的部分,其余两部分都是共性的部分。object.__new__() 创建空对象,然后return。
对象的调用,调用类的__call__()方法。
ns在加载阶段就准备好了内置ns和全局ns,只有直接阶段才会产生局部ns。
绑定方法的第一个参数都是slef,例如l1=[1,2,3]
可以list.append(l1,100)
也可以l1.append(100)一般都是后者这样使用。
dog.__dict__()
Animal.__dict__()
类的函数属性,比较特殊,是给对象用的,但是是通过绑定的方式给对象用的,绑定不同的对象就是不同的绑定方法,内存地址就不相同,但其实都是指向同一个功能。
这也是我不理解的,那么我这么说服我自己,当对象调用方法的时候其实并不是拿到的类名称空间里面的函数,而是拿到的一个拷贝,因为调用结束就会回收掉,用这样的方式达到共享方法的目的,毕竟内存地址是否相同不是我们关心的内容,只要逻辑相同即可。毕竟方法也是对象,如果一个对象拿走另一个也要用,应该也会发生竞争问题。
类内部定义的实例方法是给对象用的,当然类也可以使用。
class Animal():
def f1(self) :
pass
Animal.f1()可以无法调用函数,但是f1是绑定方法,第一个参数是自动传入的
dog=Animal()
dog.f1()#self其实就是dog本身
因此用类来调用绑定方法必须是
Animal.f1(dog)#需要两步,也没有人闲的这么调用。

浙公网安备 33010602011771号