zoukankan      html  css  js  c++  java
  • 元类

    一:什么是元类?

    在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)
    
  • 相关阅读:
    作业8: 软件工程学习总结
    用户体验——南通大学教务学生管理系统
    “构建之法互动游戏”感想
    第二次作业
    音乐播放器的发展演变
    C++用法的学习心得
    一、最后一次作业:软件工程学习总结
    设计一款给爸爸妈妈用的手机
    附加题1—— 我想搞懂的软工问题
    计算机病毒软件的发展演变
  • 原文地址:https://www.cnblogs.com/wait59/p/13634978.html
Copyright © 2011-2022 走看看