首页 > Web开发 > 详细

元类metaclass

时间:2019-03-26 00:44:27      阅读:176      评论:0      收藏:0      [点我收藏+]

 

 一 什么是元类

一切源自于一句话: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)

    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( \n)) == 0:
            raise TypeError(类中必须有文档注释,并且文档注释不能为空)

    def __call__(self, *args, **kwargs):  # self=<class ‘__main__.OldboyTeacher‘>   OldboyTeacher()的时候触发执行
        # 1、调用__new__产生一个空对象obj
        obj = self.__new__(self)  # 此处的self是类OldoyTeacher,必须传参,代表创建一个OldboyTeacher的对象obj

        # 2、调用__init__初始化空对象obj
        self.__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()

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

我们用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__则是直接跨过了他们三个
答案

 

 待续。。。

 

 

 

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

元类metaclass

原文:https://www.cnblogs.com/carlous/p/10596890.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!