zoukankan      html  css  js  c++  java
  • Python元类之由浅入深

    前言

    ​ 元类属于python面向对象编程的深层次的魔法,非常重要,它使我们可以更好的掌控类从创建到消亡的整个生命周期过程。很多框架的源码中都使用到了元类。例如 Django Framework 中的 ORM engine.

    白类 === 普通的自定义类

    什么是元类

    面向对象编程最重要的一句话:一切皆对象 过去我们都 是这样创建类的:

    class Panda(object):
        hobby= "study python"
        def __init__(self, name, age):  # initialize
            self.name = name
            self.age  = age
    
        def __str__(self):              # format 
            return "My name=%s, age=%s"%(self.name, self.age)
    

    然后再实例化获得 对象

    suosuo = Panda("suosuo", 120)
    print(suosuo)            # "My name=suosuo, age=120"
    print(type(suosuo))      # <class '__main__.Panda'>
    

    都说了一切皆对象,那请问 类:<class '__main__.Panda'> 也该是一个对象吧!

    答:没错,创建这个 Panda 类 的 类 我们称之为 元类, 也就是 这个 Panda 类是元类实例化得到的结果对象。

    print(type(Panda))      # <class 'type'>   , 即默认的唯一一个内置元类 叫 type
    

    type

    class 关键字创建 类 的流程分析

    以前我们只知道如何使用类, 现在有必要分析底层原理。手动创建一个 类

    class 关键字创建类时,必然代替我们调用了元类 Panda = type(.....), 调用时传入的参数是啥呢?

    一个类有三个关键组成部分,分别是:

    1. 类名 class_name = "Panda"
    2. 基类们 class_bases = (object, )
    3. 类的名称空间 class_dic,---> 是由执行类体代码决定的

    自定义元类 手动创建白类 Panda :

    ​ 一个类没有声明自己的元类,语言底层自动指定默认元类就是 type,

    当然我们可以自定义元类------》通过继承 type 元类的方式。然后使用 metaclass 关键字为一个 白类 指定元类

    class Mymeta(type):       # 只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
        pass
    
    class Panda(object,metaclass=Mymeta):  # Panda = Mytype("Panda", (object,),{......})
        hobby= "study python"
        def __init__(self, name, age):
            self.name = name
            self.age  = age
    
        def __str__(self):
            return "My name=%s, age=%s"%(self.name, self.age)
    

    自定义元类可以控制白类的产生过程,白类的产生过程其实就是元类调用自身 __new__(...) 方法的过程:

    class Mymeta(type):     # 只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
        def __new__(cls, class_name, class_bases, class_dic):  # def __new__(cls, *args, **kwargs):
            print(Mymeta)      # <class '__main__.Mymeta'>
            print(cls)         # <class '__main__.Mymeta'>
            print(class_name)  # Panda
            print(class_bases) # (<class 'object'>,)
            print(class_dic)   # {'__module__': '__main__', '__qualname__': 'Panda', 'hobby': 'study python', '__init__': <function Panda.__init__ at 0x02AB3780>,
            return super(Mymeta, cls).__new__(cls, class_name, class_bases, class_dic)   # 重用父类的功能
          # return super(Mymeta, cls).__new__(cls, *args, **kwargs)
        def __init__(self, class_name, class_bases, class_dic):  # 必须为 4 个参数,做自定制的
            print(self)       # <class '__main__.Panda'>  注意这里,注意这里, 注意这里
            super(Mymeta, self).__init__(class_name, class_bases, class_dic)             # 重用父类的功能
    
            if class_name.islower():
                raise TypeError('类名%s请修改为驼峰体' %class_name)
    
            if '__doc__' not in class_dic or len(class_dic['__doc__'].strip(' 
    ')) == 0:
                raise TypeError('类中必须有文档注释,并且文档注释不能为空')
    
    class Panda(object,metaclass=Mymeta):  # Panda = Mytype("Panda", (object,),{......})
        """ 类 Panda 的文档注释  """
        hobby= "study python"
        def __init__(self, name, age):
            self.name = name
            self.age  = age
    
        def __str__(self):
            return "My name=%s, age=%s"%(self.name, self.age)
        
    suosuo = Panda("suosuo", 120)
    

    需注意以下几点:

    1. 三个参数: (类名, 基类们,类的名称空间) 都自动传入自定义元类的 __new__ 和 __init__ 方法。

    2. __new__ 方法的 第一个参数 cls = 自定义元类, 而 __init__ 方法 的第一个参数 self = 白类, 也就是此自定义元类

      __new__ 方法 的执行结果。

    3. __new__ 方法 必须要有 return 语句, 调用基类的 __new__ 方法, 而 __init__ 可以没有 return 语句 (初始化)

    手动控制 白类 实例化 的过程

    储备知识: __call__

    class Foo:
        def __call__(self, *args, **kwargs):
            print(self)
            print(args)
            print(kwargs)
    
    obj=Foo()
    # 1、要想让obj这个对象变成一个可调用的对象,需要在该对象的类中定义一个方法__call__方法,该方法会在调用对象时自动触发
    # 2、调用obj的返回值就是__call__方法的返回值
    res=obj(1,2,3,x=1,y=2)
    

    由上例得知, 调用一个对象 ----》(对象 + 括号), 就是触发此对象所在类中的 __call__ 方法的执行。又因为白类 是

    元类实例化的对象,故 白类 加 括号 运行 -----》白类 实例化,也必然 触发 元类中的 __call__ 方法

    # 测试 ,查看一下关键的几个参数的值
    class Mymeta(type):
    
        def __call__(self, *args, **kwargs):
            print(self)       # <class '__main__.Panda'>
            print(args)	      # ('suosuo',)
            print(kwargs)     # {'age': 120}
    
    class Panda(object,metaclass=Mymeta):  # Panda = Mytype("Panda", (object,),{......})
        hobby= "study python"
        def __init__(self, name, age):
            self.name = name
            self.age  = age
    
        def __str__(self):
            return "My name=%s, age=%s"%(self.name, self.age)
    
    suosuo = Panda("suosuo", age=120)
    print(suosuo)                        # 结果:None , 原因为 元类 __call__ 方法无返回值
    

    注意以下几点:

    1. 调用 白类 则自动触发 元类中的 __call__ 方法
    2. 元类 __call__ 方法 的 self 为白类,*args 为白类实例化溢出的位置参数,**kwargs 为白类实例化溢出的关键字参数
    3. 白类实例化的执行结果 就是 元类 __call__ 方法的返回结果 ,

    默认 白类 实例化 Panda("suosuo", age=120) -----》 元类的 __call__ 方法 会做三件事:

    1. 产生一个 空 对象 obj
    2. 调用 __init__ 方法 初始化 对象 obj
    3. 返回 初始化好的 obj
    class Mymeta(type): #只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
        def __call__(self, *args, **kwargs):
            # 1、调用__new__产生一个空对象obj
            obj = self.__new__(self)  # 此处必须传参, self = 白类,代表创建一个白类对象
    
            # 2、调用__init__初始化空对象obj#
            self.__init__(obj, *args, **kwargs)
    
            # 3、返回初始化好的对象obj
            return obj
    
    class Panda(object,metaclass=Mymeta):  # Panda = Mytype("Panda", (object,),{......})
        hobby= "study python"
        def __init__(self, name, age):
            print(self)                    # <__main__.Panda object at 0x0106E790> 
            self.name = name
            self.age  = age
    
    suosuo = Panda("suosuo", age=120)  
    print(suosuo)                          # <__main__.Panda object at 0x0220E790>
    

    上例 元类的 __call__ 方法 就相当于一个 白类实例化 创建对象的 模板 , 我们 可以在该基础上改写 __call__ 方法的 逻辑

    从而 控制 白类 实例化 Panda("suosuo", age=120) 的过程。

    比如将 Panda 的 对象的所有属性都变为私有的

    class Mymeta(type): #只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
        def __call__(self, *args, **kwargs):
            # 1、调用__new__产生一个空对象obj
            obj = self.__new__(self)  # 此处必须传参, self = 白类,代表创建一个白类对象
    
            # 2、调用__init__初始化空对象obj#
            self.__init__(obj, *args, **kwargs)
    
            # 在初始化之后,obj.__dict__里就有值了
            obj.__dict__ = {'_%s__%s' % (self.__name__, k): v for k, v in obj.__dict__.items()}
    
            # 3、返回初始化好的对象obj
            return obj
    
    class Panda(object,metaclass=Mymeta):  # Panda = Mytype("Panda", (object,),{......})
        hobby= "study python"
        def __init__(self, name, age):
            print(self)
            self.name = name
            self.age  = age
    
    suosuo = Panda("suosuo", age=120) 
    print(suosuo.__dict__)                 # {'_Panda__name': 'suosuo', '_Panda__age': 120}
    

    结合 元类 属性的查找顺序

    在学习完元类后,其实我们用class自定义的类也全都是对象(包括object类本身也是元类type的 一个实例,

    可以用type(object)查看),我们学习过继承的实现原理,如果把类当成对象去看,

    将下述继承应该说成是:对象 Panda 继承对象 Footer,对象 Footer 继承对象Skin,对象 Skin 继承对象object

    class Mymeta(type):       #只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
        n = 444
        def __call__(self, *args, **kwargs):
            obj = self.__new__(self)
            self.__init__(obj, *args, **kwargs)
            return obj        # 必须 有 返回值
    
    class Skin(object):
        n = 333
    
    class Footer(Skin):
        n = 222
    
    class Panda(Footer,metaclass=Mymeta):  # Panda = Mytype("Panda", (object,),{......})
        n = 111
        def __init__(self, name, age):
            self.name = name
            self.age  = age
    
    suosuo = Panda("suosuo", age=120)
    print(Panda.n)                        # 注意是 类 属性 
    

    于是属性查找应该分成两层,一层是对象层(基于c3算法的MRO)的查找,另外一个层则是类层(即元类层)的查找

    白类类名 查找顺序:

    1. 先白类层 Panda--> Footer -->Skin--> object
    2. 再元类层 Mymeta--> type

    依据上述总结,我们来分析下元类Mymeta中__call__里的self.__new__ 方法的查找

    class Mymeta(type): #只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
        n = 444
        def __call__(self, *args, **kwargs):
            obj = self.__new__(self)
            print(self.__new__ is object.__new__) #True
            self.__init__(obj, *args, **kwargs)
            return obj
    
    class Skin(object):
        n = 333
    
    class Footer(Skin):
        n = 222
    
    class Panda(Footer,metaclass=Mymeta):  # Panda = Mytype("Panda", (object,),{......})
        n = 111
        def __init__(self, name, age):
            self.name = name
            self.age  = age
    
    suosuo = Panda("suosuo", age=120)
    print(Panda.n)                        # 注意是 类 属性 
    

    总结,Mymeta下的__call__里的self.__new__在 Panda、Footer、Skin 里都没有找到__new__的情况下,

    会去找object里的__new__,而object下默认就有一个__new__,所以即便是之前的类均未实现__new__,

    也一定会在object中找到一个,根本不会、也根本没必要再去找元类Mymeta->type中查找__new__

    我们在元类的__call__中也可以用object.__new__(self) 去造空对象

    但还是推荐在__call__中使用self.__new__(self)去创造空对象,因为这种方式会检索三个类Panda--->Footer--->Skin,而object.__new__则是直接跨过了他们三个

    class Mymeta(type): #只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
        n = 444
        def __new__(self, *args, **kwargs):
            obj=type.__new__(self, *args,**kwargs) # 必须按照这种传值方式
            
    #        return obj                           # 只有在返回值是type的对象时,才会触发下面的__init__
            return 123
    
        def __init__(self,class_name,class_bases,class_dic):
            print('run。。。')
    
    class Panda(object,metaclass=Mymeta):   # Panda = Mytype("Panda", (object,),{......})
        n = 111
        def __new__(self, *args, **kwargs):
    #         return super().__new__(self)  # 只有返回是 object的对象时,才会触发下方的__init__方法
        	return 456
    
        def __init__(self, name, age):
            self.name = name
            self.age  = age
    
    suosuo = Panda("suosuo", age=120)
    print(suosuo)                           # 'int' object is not callable                        
    

    注意:

    1. 只有 元类的 __new__ 方法只有返回 type 的对象时, 才会触发 元类的 __init__ 方法, 同理。
    2. 只有 白类的 __new__ 方法只有返回 object 的对象时, 才会触发 白类的 __init__ 方法
    3. type.__new__(cls,class_name, class_bases, class_dic) 必须添加这四个参数, 而 object.__new__(self) 只能添加这一个参数,代表创建谁的对象

    练习

    练习一:在元类中控制把自定义类的数据属性都变成大写

    class Mymetaclass(type):
        def __new__(cls,name,bases,attrs):
            update_attrs={}
            for k,v in attrs.items():
                if not callable(v) and not k.startswith('__'):
                    update_attrs[k.upper()]=v
                else:
                    update_attrs[k]=v
            return type.__new__(cls,name,bases,update_attrs)
    
    class Chinese(metaclass=Mymetaclass):
        country='China'
        tag='Legend of the Dragon' #龙的传人
        def walk(self):
            print('%s is walking' %self.name)
    
    
    print(Chinese.__dict__)
    '''
    {'__module__': '__main__',
     'COUNTRY': 'China', 
     'TAG': 'Legend of the Dragon',
     'walk': <function Chinese.walk at 0x0000000001E7B950>,
     '__dict__': <attribute '__dict__' of 'Chinese' objects>,                                         
     '__weakref__': <attribute '__weakref__' of 'Chinese' objects>,
     '__doc__': None}
    '''
    

    练习二:在元类中控制自定义的类无需__init__方法

    1. 元类帮其完成创建对象,以及初始化操作;
    2. 要求实例化时传参必须为关键字形式,否则抛出异常TypeError: must use keyword argument
    3. key作为用户自定义类产生对象的属性,且所有属性变成大写
    class Mymetaclass(type):
        # def __new__(cls,name,bases,attrs):
        #     update_attrs={}
        #     for k,v in attrs.items():
        #         if not callable(v) and not k.startswith('__'):
        #             update_attrs[k.upper()]=v
        #         else:
        #             update_attrs[k]=v
        #     return type.__new__(cls,name,bases,update_attrs)
    
        def __call__(self, *args, **kwargs):
            if args:
                raise TypeError('must use keyword argument for key function')
            obj = object.__new__(self) #创建对象,self为类Foo
    
            for k,v in kwargs.items():
                obj.__dict__[k.upper()]=v
            return obj
    
    class Chinese(metaclass=Mymetaclass):
        country='China'
        tag='Legend of the Dragon' #龙的传人
        def walk(self):
            print('%s is walking' %self.name)
    
    
    p=Chinese(name='egon',age=18,sex='male')
    print(p.__dict__)
    

    练习三:在元类中控制自定义的类产生的对象相关的属性全部为隐藏属性

    class Mymeta(type):
        def __init__(self,class_name,class_bases,class_dic):
            #控制类Foo的创建
            super(Mymeta,self).__init__(class_name,class_bases,class_dic)
    
        def __call__(self, *args, **kwargs):
            #控制Foo的调用过程,即Foo对象的产生过程
            obj = self.__new__(self)
            self.__init__(obj, *args, **kwargs)
            obj.__dict__={'_%s__%s' %(self.__name__,k):v for k,v in obj.__dict__.items()}
    
            return obj
    
    class Foo(object,metaclass=Mymeta):  # Foo=Mymeta(...)
        def __init__(self, name, age,sex):
            self.name=name
            self.age=age
            self.sex=sex
    
    
    obj=Foo('egon',18,'male')
    print(obj.__dict__)
    

    练习四: 基于元类实现单例模式

    #步骤五:基于元类实现单例模式
    # 单例:即单个实例,指的是同一个类实例化多次的结果指向同一个对象,用于节省内存空间
    # 如果我们从配置文件中读取配置来进行实例化,在配置相同的情况下,就没必要重复产生对象浪费内存了
    #settings.py文件内容如下
    HOST='1.1.1.1'
    PORT=3306
    
    #方式一:定义一个类方法实现单例模式
    import settings
    
    class Mysql:
        __instance=None
        def __init__(self,host,port):
            self.host=host
            self.port=port
    
        @classmethod
        def singleton(cls):
            if not cls.__instance:
                cls.__instance=cls(settings.HOST,settings.PORT)
            return cls.__instance
    
    
    obj1=Mysql('1.1.1.2',3306)
    obj2=Mysql('1.1.1.3',3307)
    print(obj1 is obj2) #False
    
    obj3=Mysql.singleton()
    obj4=Mysql.singleton()
    print(obj3 is obj4) #True
    
    
    
    #方式二:定制元类实现单例模式
    import settings
    
    class Mymeta(type):
        def __init__(self,name,bases,dic): #定义类Mysql时就触发
    
            # 事先先从配置文件中取配置来造一个Mysql的实例出来
            self.__instance = object.__new__(self)  # 产生对象
            self.__init__(self.__instance, settings.HOST, settings.PORT)  # 初始化对象
            # 上述两步可以合成下面一步
            # self.__instance=super().__call__(*args,**kwargs)
    
    
            super().__init__(name,bases,dic)
    
        def __call__(self, *args, **kwargs): #Mysql(...)时触发
            if args or kwargs: # args或kwargs内有值
                obj=object.__new__(self)
                self.__init__(obj,*args,**kwargs)
                return obj
    
            return self.__instance
    
    
    
    
    class Mysql(metaclass=Mymeta):
        def __init__(self,host,port):
            self.host=host
            self.port=port
    
    
    
    obj1=Mysql() # 没有传值则默认从配置文件中读配置来实例化,所有的实例应该指向一个内存地址
    obj2=Mysql()
    obj3=Mysql()
    
    print(obj1 is obj2 is obj3)
    
    obj4=Mysql('1.1.1.4',3307)
    
    
    
    #方式三:定义一个装饰器实现单例模式
    import settings
    
    def singleton(cls): #cls=Mysql
        _instance=cls(settings.HOST,settings.PORT)
    
        def wrapper(*args,**kwargs):
            if args or kwargs:
                obj=cls(*args,**kwargs)
                return obj
            return _instance
    
        return wrapper
    
    
    @singleton # Mysql=singleton(Mysql)
    class Mysql:
        def __init__(self,host,port):
            self.host=host
            self.port=port
    
    obj1=Mysql()
    obj2=Mysql()
    obj3=Mysql()
    print(obj1 is obj2 is obj3) #True
    
    obj4=Mysql('1.1.1.3',3307)
    obj5=Mysql('1.1.1.4',3308)
    print(obj3 is obj4) #False
    

  • 相关阅读:
    git知识点总结
    自动化进阶
    unittest单元测试框架
    自动化测试模型
    webdriver
    python文件处理
    uva 11077 置换
    poj 1066 Treasure Hunt
    poj 2661 Factstone Benchmark
    hdu 4180
  • 原文地址:https://www.cnblogs.com/shiwei1930/p/11717596.html
Copyright © 2011-2022 走看看