一:什么是元类?
在Python中一切皆对象(在linux中一切皆文件),先定义一个类,然后分析一下
class Teacher(object):
school = 'sh'
def __init__(self,name,age):
self.name = name
self.age = age
def talk(self):
print(f"{self.name}正在交流")
所有的对象都是通过调用类而得到的(调用类的过程也叫做,类的实例化)
t = Teacher('alen',28)
print(t) # 查看对象t的类:<class '__main__.Teacher'>
若果在python中一切皆对象的话,那么Teacher这个类也是一个对象,那么它是通过什么方式(或则形式实例化得到的呢?),它是通过调用type这个类实现的,type这个类就是元类
print(type(Teacher)) # 查看Teacher的类:<class 'type'>
那么我就可以分析的得出产生Teacher这个过程,一定是调用了元类实例化得到的(Teacher = type(.....))
二:class关键字底层实现逻辑
在python中一切皆对象,我们可以分析出class 关键字定义类本身也是一个对象,负责产生该对象的类称呼为元类,内置的元类type
class关键字在帮我们创建类时,必然帮我们调用了元类Teacher = type(.....),那调用type时传入的参数是什么?必然是类的关键组成部分,一个类有三大组成部分,分别是:类名,基类,类的名称空间,类的名称空间是执行类体代码而得到的,调用type类时会依次传入上面三个参数
1:拿到类名 class_name = Teacher
2:拿到类的基类 class_bases = Object
3:执行类体代码,拿到类的名称空间 class_dict = {...}
4:调用元类得到类 Teacher = type(class_name,class_bases,class_dict,)
扩展
exec:三个参数
参数一:包含一系列python代码的字符串
参数二:全局作用域(字典形式),如果不指定,默认为globals()
参数三:局部作用域(字典形式),如果不指定,默认为locals()
可以把exec命令的执行当成是一个函数的执行,会将执行期间产生的名字存放于局部名称空间中
g={
'x':1,
'y':2
}
l={}
exec('''
global x,z
x=100
z=200
m=300
''',g,l)
print(g) #{'x': 100, 'y': 2,'z':200,......}
print(l) #{'m': 300}
三:自定义元类控制类Teacher的创建
一个类没有声明自己的元类,默认它的元类就是type,除了使用内置元类type,我们可以通过继承type来自定义元类,然后使用metaclass关键字参数为一个类指定元类
class Mymeta(type):
pass
class Teacher(object,metaclass=Mymeta):
school = 'sh'
def __init__(self,name,age):
self.name = name
self.age= age
def talk(self):
print(f"{self.name}正在交流")
自定义元类可以控制类的产生过程,类的产生过程其实就是元类的调用过程,即Teacher = type(Teacher,Object,{...}),调用Mymeta会产生一个空对象Teacher,让后连同调用Mymeta括号内的参数一同传给Mymeta的__init__方法,完成初始化,也是我们可以
class Mymeta(type): # 只要继承type类,才能称之为一个元类,否则就是一个普通的自定义类
def __init__(self,class_name,class_bases,class_dict):
print(self)
print(class_bases)
print(class_dict)
super(Mymeta,self).__init__(class_name,class_bases,class_dic) # 继承重用父类功能
if class_name.islower():
raise TypeError(f"类名{class_name}必须使用驼峰体")
class Teacher(object,metaclass=Mymeta): # Teacher = type('Teacher',(Object,),{...})
"""
Teacher这个类的文档注释
"""
school = 'sh'
def __init__(self,name,age):
self.name = name
self.age = age
def talk(self):
print(f"{self.name}正在交流")
四:自定义元类控制类Teacher的调用
储备知识:_call_
class Foo:
def __call__(self,*args,**kwargs):
print(self)
print(*args)
print(**kwargs)
obj = Foo()
# 想让obj这个对象变成一个可调用的对象,需要在该对象类中定义一个方法__call__,该方法会在调用对象时自动触发
# 调obj返回的值就是__call__方法的返回值
result = obj(1,2,3,3,x=1,b=2,c=3)
print(result)
由上可知,调用一个对象,就是触发对象所在类中的__call__方法的执行,若果把Teacher也当做一个对象,那么Teacer这个对象的类中必然存在一个__call__方法
class Mymeta(type): #只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
def __call__(self,*args,**kwargs):
print(self) # <class '__main__.OldboyTeacher'>
print(args) # ('alen', 18)
print(kwargs) # {}
return 123
class Teacher(object,metaclass=Mymeta):
school = 'sh'
def __init__(self,name,age):
self.name = name
self.age = age
def talk(self):
print(f"{self.name}正在交流")
# 调用Teacher就是在调用Teacher类中的__call__方法
# 然后将Teacher传给self,溢出的位置参数由*接收,溢出的关键字参数由**接收
# 调用Teacher的返回值就是调用__call__的返回值
t = Teacher('alen',18)
print(t) 123
默认地,调用t = Teacher('alen',18)会做三件事
1: 产生一个空对象obj
2:调用__init__方法初始化对象obj
3: 返回初始化好的obj
对应,Teacher类(Teacher的类是Mymeta)中的__call__方法应该做这三件事
class Mymeta(type): #只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
def __call__(self,*args,**kwargs) # #self=<class '__main__.Teacher'>
# 1:调用__new__产生一个空对象obj
obj = self.__new__(self) # 此处的slef类是Teacher,必须传参,代表创建一个Teacher的对象obj
# 2:调用__init__初始化对象obj
self.__init__(obj,*args,**kwargs)
# 3:初始化好的对象obj
return obj
class Teacher(Object,metaclass=Mymeta):
school = 'sh'
def __init__(self,name,age):
self.name = name
self.age = age
def talk(self):
print(f"{self.name}正在交流中")
t = Teacher('alen',22)
print(t.__dict__) #{'name': 'egon', 'age': 18}
上面_call__相当于个模板,我可以在这个基础上改写__call__方法.从而控制调用Teacher的过程,例如将Teacher的对象的所有属性都变成私有
class Mymeta(type): # 只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
def __call__(self,*args,**kwargs): # self=<class '__main__.Teacher'>
#1、调用__new__产生一个空对象obj
obj = self.__new__(self):
#2、调用__init__初始化空对象obj
slef.__init__(obj,*args,**kwargs)
# 在初始化之后,obj.__dict__里就有值了
obj.__dict__{"{}{}".format(self.__name__,k):v for k,v in obj.__dict__.items()}
#3: 返回初始化好的对象obj
return obj
class Teacher(Ojbect,metaclass=Mymeta):
school = sh
def __init__(self,name,age):
self.name = name
self.age = age
def talk(self):
print(f"{self.name}正在交流")
t = Teacher('alen',28)
print(t.__dict__) # #{'_Teacher__name': 'egon', 'Teacher__age': 18}
上例中涉及到查找属性的问题,比如self._new_,请看下一小节
五:属性查找
结合python继承的实现原理+元类重新看属性的查找应该是什么样子呢???
在学习完元类后,其实我们用class自定义的类也全都是对象(包括object类本身也是元类type的 一个实例,可以用type(object)查看),我们学习过继承的实现原理,如果把类当成对象去看,将下述继承应该说成是:对象Teacher继承对象Foo,对象Foo继承对象Bar,对象Bar继承对象object
class Mymeta(type): #只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
n = 444
def __init__(self,*args,**kwargs): #self=<class '__main__.Teacher'>
obj = self.__new__(self)
self.__init__(obj,*args,**kwargs)
return obj
class Bar(object):
n = 333
class Foo(Bar):
n = 222
class Teacher(Foo,metaclass=Mymeta):
n = 111
school = 'sh'
def __init__(self,name,age):
self.name = name
sefl.age = age
def talk(self):
print(f"{self.name}正在运行")
print(Teacher.n) # 自下而上依次注释各个类中的n=xxx,然后重新运行程序,发现n的查找顺序为Teacher->Foo->Bar->object->Mymeta->type
于是属性查找应该分成两层,一层是对象层(基于c3算法的MRO)的查找,另外一个层则是类层(即元类层)的查找
查找顺序
# 先对象层 Teacher -> Foo - > BAr
# 后元类层:Mymeta -> type
根据上述总结,我们来分析下元类Mymeta中 _ _call_ _ 里的 self._ _new_ _ 的查找
class Mymeta(type):
n=444
def __call__(self, *args, **kwargs): #self=<class '__main__.OldboyTeacher'>
obj=self.__new__(self)
print(self.__new__ is object.__new__) #True
class Bar(object):
n=333
# def __new__(cls, *args, **kwargs):
# print('Bar.__new__')
class Foo(Bar):
n=222
# def __new__(cls, *args, **kwargs):
# print('Foo.__new__')
class OldboyTeacher(Foo,metaclass=Mymeta):
n=111
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)
# def __new__(cls, *args, **kwargs):
# print('OldboyTeacher.__new__')
OldboyTeacher('egon',18) #触发OldboyTeacher的类中的__call__方法的执行,进而执行self.__new__开始查找
总结,Mymeta下的__call__里的self.__new__在OldboyTeacher、Foo、Bar里都没有找到__new__的情况下,会去找object里的__new__,而object下默认就有一个__new__,所以即便是之前的类均未实现__new__,也一定会在object中找到一个,根本不会、也根本没必要再去找元类Mymeta->type中查找__new__
我们在元类的__call__中也可以用object.__new__(self)去造对象
但我们还是推荐在__call__中使用self.__new__(self)去创造空对象,因为这种方式会检索三个类Teacher->Foo->Bar,而object.__new__则是直接跨过了他们三个
最后说明一点
class Mymeta(type):
n = 444
def __new__(cls,*args,**kwargs):
obj = type.__new__(cls,*args,**kwargs) # 必须按照这种传值方式
print(obj.__dict__)
return obj # 只有在返回值是type的对象时,才会触发下面的__init__
return 123
def __init__(self,class_name,class_bases,class_dic):
print("runing")
class Teacher(object,metaclass=Mymeta): # Teacher = Mymeta('Tacher',(object,),{})
n = 111
school = 'oldboy'
def __init__(self,name,age):
self.name = name
self.age = age
def talk(self):
print(f"{self.name}正在交流")
print(type(Mymeta)) #<class 'type'>
# 产生Teacher的过程就是在调用Mymeta,Mymeta也是type类的一个对象,那么Mymeta之所以调用,一定是元类type中有一个__call__ 方法
# 该方法同样需要做至少三件事
# class type:
# def __call__(self, *args, **kwargs): #self=<class '__main__.Mymeta'>
# obj=self.__new__(self,*args,**kwargs) # 产生Mymeta的一个对象
# self.__init__(obj,*args,**kwargs)
# return obj
六:单例模式
1:什么是单例模式?
在调用class的过程无论怎么实例化?结果就是只会产生一个对象
单例:即单个实例,指的是同一个类实例化多次的结果指向同一个对象,用于节省内存空间
# 如果我们从配置文件中读取配置来进行实例化,在配置相同的情况下,就没必要重复产生对象浪费内存了
实现单例模式一:
settings.py文件
IP = '8.8.8.8'
PORT = 53
import settings
class Mysql:
_instance = None
def __init__(self,ip,port):
self.ip = ip
self.port = port
@classmethod
def singleton(cls):
if not cls.__instance:
cls.__intance = cls(settings.IP,sttings,port)
return cls.__instance
s1 = Mysql('1.2.3.4',3307)
s2 = Mysql('1.11.3.2',3306)
print(s1 is s2) # False
s3 = Mysql.singleton()
s4 = Mysql.singleton()
print(s3 is s4) # True
方式二:定制元类实现单例模式
import settings
class Mymeta(type):
def __init__(self,name,bases,dic): # 定义类Mysql时就触发
self.__instance = object.__new__(self) # 产生空对象
self.__init__(self.__instance,settings.IP,settins.PORT) # 初始化对象
# super().__init__(name,bases,dic)
def __call__(self,*args,**kwargs):# Mysql(...)时触发
if args or kwargs: # args或kwargs内有值
obj.__new__(self)
self.__init__(obj,*args,**kwargs)
else:
retrun obj
class Mysql(metaclass=Mymeta):
def __init__(self,ip,port):
self.ip = ip
self.port = port
s1 = Mysql() # 没有传值则默认从配置文件中读配置来实例化,所有的实例应该指向一个内存地址
s2 = Mysql()
s2 = Mysql()
print(s1 is s2 is s3)
s4 = Mysql('13,3,11,44',3306)
定义一个装饰器实现单例模式
import settins
def singleton(cls): # cls = Mysql
_instance = cls(settings.IP,settings.PORT)
def Wrapper(*args,**kwargs):
if args or kwargs:
ojb = cls(*args,**kwargs)
return obj
return wrapper
@singleton
class Mysql:
def __init__(self,ip,port):
self.ip = ip
self.port = port
s1 = Mysql()
s2 = Mysql()
s3 = Mysql()
print(s1 is s2 is s3) # True
s4 = Myql('8.8.8.8',3306)
s5 = Mysql('222.222.222.222',3308)