zoukankan      html  css  js  c++  java
  • 元类metaclass

     一 什么是元类

    一切源自于一句话:python中一切皆为对象。让我们先定义一个类,然后逐步分析

    class OldboyTeacher(object):
        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)
    t1=OldboyTeacher('egon',18)
    print(type(t1)) #查看对象t1的类是<class '__main__.OldboyTeacher'>

    所有的对象都是实例化或者说调用类而得到的(调用类的过程称为类的实例化),比如对象t1是调用类OldboyTeacher得到的。

    如果一切皆为对象,那么类OldboyTeacher本质也是一个对象,既然所有的对象都是调用类得到的,那么OldboyTeacher必然也是调用了一个类得到的,这个类称为元类

    print(type(OldboyTeacher)) # 结果为<class 'type'>,证明是调用了type这个元类而产生的OldboyTeacher,即默认的元类为type

     

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

    我们用class关键字定义的类本身也是一个对象,负责产生该对象的类称之为元类(元类可以简称为类的类),内置的元类为type

    class关键字在帮我们创建类时,必然帮我们调用了元类OldboyTeacher=type(...),那调用type时传入的参数是什么呢?必然是类的关键组成部分,一个类有三大组成部分,分别是

    1、类名class_name='OldboyTeacher'

    2、基类们class_bases=(object,)

    3、类的名称空间class_dic,类的名称空间是执行类体代码而得到的

    调用type时会依次传入以上三个参数

    #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}
    exec模拟实现创建类

    三 自定义元类控制类OldboyTeacher的创建和调用

     class Mymeta(type): # 只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类

      # def __init__(self,*args,**kwargs ):  # 简写
       #     super(Mymeta, self).__init__(*args,**kwargs)
      # type的__call__会调用Mymeta的__init__和type代码中的__new__创建OldboyTeacher对象(如果__new__没有重写) def __init__(self, class_name, class_bases, class_dic): # class OldboyTeacher(object, metaclass=Mymeta)时触发 # print(self) #<class '__main__.OldboyTeacher'> # print(class_bases) #(<class 'object'>,) # print(class_dic) #{'__module__': '__main__', '__qualname__': 'OldboyTeacher', 'school': 'oldboy', # '__init__': <function OldboyTeacher.__init__ at 0x102b95ae8>, 'say': <function OldboyTeacher.say at 0x10621c6a8>} 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('类中必须有文档注释,并且文档注释不能为空') def __call__(cls, *args, **kwargs): # cls=<class '__main__.OldboyTeacher'> OldboyTeacher()的时候触发执行 # 1、调用__new__产生一个空对象obj obj = cls.__new__(cls) # 此处的cls是类OldoyTeacher,必须传参,代表创建一个OldboyTeacher的对象obj,
    # 2、调用__init__初始化空对象obj cls.__init__(obj, *args, **kwargs) # 3、返回初始化好的对象obj 就是OldboyTeacher()的实例化对象 old_tea return obj class OldboyTeacher(object, metaclass=Mymeta): # OldboyTeacher=Mymeta('OldboyTeacher',(object),{...}) """ 类OldboyTeacher的文档注释 """ 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) old_tea = OldboyTeacher(‘cp’,19)

    简单来说:(type指元类,默认就是type)

    - 类的创建  (class 类 )
      type.__init__
    - 对象的创建 ( 类() )
      type.__call__
        - 类.__new__
        - 类.__init__

    如果一类自己或基类中指定了metaclass,那么该类就是由metaclass指定的type或mytype创建。

     1 class MyType(type):
     2     def __init__(self,*args,**kwargs):
     3         print('111')
     4         super(MyType,self).__init__(*args,**kwargs)
     5 
     6 
     7 class Base(object, metaclass=MyType):
     8     pass
     9 
    10 class Foo(Base):
    11     pass
    12 
    13 # 打印两次111
    父类指定metaclass
     1         class MyType(type):
     2         def __init__(self,*args,**kwargs):
     3             print('111')
     4             super(MyType,self).__init__(*args,**kwargs)
     5 
     6 
     7     # class Base(object, metaclass=MyType):
     8     #     pass
     9 
    10     Base = MyType('Base',(object,),{})
    11 
    12     class Foo(Base):
    13         pass
    14 
    15 # ============================
    16 
    17         class MyType(type):
    18         def __init__(self,*args,**kwargs):
    19             print('111')
    20             super(MyType,self).__init__(*args,**kwargs)
    21 
    22 
    23     def with_metaclass(arg):
    24         Base = MyType('Base',(arg,),{})
    25         return Base
    26 
    27     class Foo(with_metaclass(object)):
    28         pass
    两种变形的metaclass指定方法

     

    四 自定义元类的类属性查找

    我们用class自定义的类也全都是对象(包括object类本身也是元类type的 一个实例,可以用type(object)查看),我们学习过继承的实现原理,如果把类当成对象去看,将下述继承应该说成是:对象OldboyTeacher继承对象Foo,对象Foo继承对象Bar,对象Bar继承对象object。

    class Mymeta(type): 
        n=444
    
        def __call__(self, *args, **kwargs): #self=<class '__main__.OldboyTeacher'>
            obj=self.__new__(self)
            self.__init__(obj,*args,**kwargs)
            return obj
    
    class Bar(object):
        n=333
    
    class Foo(Bar):
        n=222
    
    class OldboyTeacher(Foo,metaclass=Mymeta):
        n=111
    
        school='oldboy'
    
        def __init__(self,name,age):
            self.name=name
    
    
       def say(self):
            print('%s says welcome to the oldboy to learn Python' %self.name)
    
    
    print(OldboyTeacher.n) 
    #自下而上依次注释各个类中的n=xxx,然后重新运行程序,发现n的查找顺序为OldboyTeacher->Foo->Bar->object->Mymeta->type

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

    #查找顺序:
    #1、先对象层:OldoyTeacher->Foo->Bar->object
    #2、然后元类层:Mymeta->type
    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__的查找
    总结,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)去创造空对象,因为这种方式会检索三个类OldboyTeacher->Foo->Bar,而object.__new__则是直接跨过了他们三个
    答案

    五 案例

    元类属于python最深层的语言特性,一般开发中很少用到,下面举一些用到元类的案例。

    # 自定义Form表单
    class RegisterForm(Form):
        name = simple.StringField(
            label='用户名',
            validators=[
                validators.DataRequired()
            ],
            widget=widgets.TextInput(),
            render_kw={'class': 'form-control'},
            # default='alex'
        )
    
    # Form指定了元类,Form其子类也会继承指定的元类。
    class Form(with_metaclass(FormMeta, BaseForm)):
        Meta = DefaultMeta
    
    # FormMeta源码
    class FormMeta(type):
        def __init__(cls, name, bases, attrs):
            type.__init__(cls, name, bases, attrs)  # 先重用父类的初始化方法,防止需要用到父类的功能
            cls._unbound_fields = None 
            cls._wtforms_meta = None
    
    # 该源码详析请参见另一篇博文 
    wtforms中Form

    参考:http://www.cnblogs.com/linhaifeng/articles/8029564.html

  • 相关阅读:
    js中的replace替换全部
    Oracle中创建数据链
    Hbuildx+vue+axios+element ui初学部署
    html5抠图
    Oracle误删除数据的恢复方法
    vs 生成项目自动关闭当前运行程序
    Mvc项目在iis上面显示文件夹 输入地址页面也打不开
    FastReport快速打印(.net)
    脚本之家
    VS自定义作者、创建时间
  • 原文地址:https://www.cnblogs.com/carlous/p/10596890.html
Copyright © 2011-2022 走看看