元类

一:什么是元类?

在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)的查找,另外一个层则是类层(即元类层)的查找

image-20200818162827655

查找顺序
# 先对象层  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)去造对象

image-20200818163331801

但我们还是推荐在__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)
posted @ 2020-09-08 20:11  为了等  阅读(174)  评论(1编辑  收藏  举报