详解python metaclass(元类)
元编程,一个听起来特别酷的词,强大的Lisp在这方面是好手,对于Python,尽管没有完善的元编程范式,一些天才的开发者还是创作了很多元编程的魔法。Django的ORM就是元编程的一个很好的例子。
本篇的概念和例子皆在Python3.6环境下
一切都是对象
Python里一切都是对象(object),基本数据类型,如数字,字串,函数都是对象。对象可以由类(class)进行创建。既然一切都是对象,那么类是对象吗?
是的,类也是对象,那么又是谁创造了类呢?答案也很简单,也是类,一个能创作类的类,就像上帝一样,开启了万物之始。这样的类,称之为元类(classmeta)。
类的定义
对象是通过类创建的,这个很好理解。例如下面的代码:
classBar(object): pass bar=Bar() print(bar,bar.__class__)#<__main__.Barobjectat0x101eb4630>print(Bar,Bar.__class__)#
可以看见对象bar是类Bar创建的实例。然而Bar,看起来却是由一个叫type的类创建的实例。即bar<--Bar<--type。
上面的例子,对象是动态创建的,类则是通过关键字class声明定义的。class关键字背后的玄机是什么呢?
实际上,classBar(object)这样的代码,等价于Bar=type('Bar',(objects,),{})
即类type通过实例化创建了它的对象Bar,而这个Bar恰恰是一个类。这样能创建类的类,就是Python的元类。
从创建Bar的代码上来看,元类type的__init__方法有3个参数,
- 第一个是创建的类的名字
- 第二个是其继承父类的元类列表,
- 最后就是一个属性字典,即该类所具有的属性。
type元类
type是小写,因而很容易误以为它是一个函数。通过help(type)可以看到它的定义如下:
classtype(object): """ type(object_or_name,bases,dict) type(object)->theobject'stype type(name,bases,dict)->anewtype """ def__init__(cls,what,bases=None,dict=None):#knownspecialcaseoftype.__init__ """ type(object_or_name,bases,dict) type(object)->theobject'stype type(name,bases,dict)->anewtype #(copiedfromclassdoc) """ pass @staticmethod#knowncaseof__new__ def__new__(*args,**kwargs):#realsignatureunknown """Createandreturnanewobject.Seehelp(type)foraccuratesignature.""" pass
如前所述,__init__方法接受三个参数,type实例化的过程,会创建一个新的类。创建类的代码来自__new__方法,它的参数其实和__init__,一样。至于它们之间有什么关系,后面再做介绍。目前只要知道,当调用type进行实例化的时候,会先自动调用__new__方法,然后再接着调用__init__方法,在类外面来看,最终会实例化一个对象,这个对象是一个类。
从type的定义来看,它继承object,Python3的所有类,都继承来着object,类type也是object的实例,令人奇怪的是,object既是类也是对象,它也是由type实例化而来。有一种鸡生蛋,蛋生鸡的悖论。暂且先不管,只要知道所有类的顶级继承来自object就好。
自定义元类
既然元类可以创建类,那么自定义元类就很简单了,直接继承类type即可。先看下面一个例子:
classMyType(type): pass classBar(object,metaclass=MyType): pass print(MyType,MyType.__class__)#print(Bar,Bar.__class__)#
可以看到,Bar在声明的时候,指定了其元类,此时的类Bar的__class__属性不再是type,而是MyType。即之前定义Bar的代码不再是Bar=type('Bar',(objects,),{}),而是Bar=MyType('Bar',(objects,),{})。创建的元类的代码是MyType=type('MyType',(objects,),{})。
如果一个类没有显示的指定其元类,那么会沿着继承链寻找父类的元类,如果一直找不到,那么就使用默认的type元类。
元类冲突
每个类都可以指定元类,但是父类和子类的元类要是一条继承关系上的,否则会出现元类冲突。并且这个继承关系中,以继承最后面的元类为其元类。
元类的查找顺序大致为,先查看其继承的父类,找到父类的元类即停止。若直接父类没有元类,直到顶级父类object,此时父类(object)的元类是type(basemetaclass),再看其自身有没有指定元类(submetaclass),如果指定了元类(submetaclass),再对比这个子元类(submetaclass)和父元类(basemetaclass),如果它们毫无继承关系,那么将会抛出元类冲突的错误。如果指定的子元类是父元类的父类,那么将会使用父元类,否则将使用期指定的子元类。
即submetaclass<-basemetaclass使用submetaclass作为最终元类,
若basemetaclass<-submetaclass,使用basemetaclass作为最终元类,
两者无继承关系,抛出冲突。
有点像绕口令,且看代码例子
classMyType(type): pass #等价于MyType=type('MyType',(object,),{}) classBar(object,metaclass=MyType): pass #等价于Bar=MyType('Bar',(object,),{}) classFoo(Bar): pass #等价于Foo=MyType('Foo',(Foo,object,),{}) print(Bar,Bar.__class__)#print(Foo,Foo.__class__)#
Bar的父元类(basemetaclass)type,指定子元类(submetaclass)是MyType,MyType继承自type,所以Bar的元类是MyType。
又如:
classMyType(type): pass classBar(object,metaclass=MyType): pass classFoo(Bar,metaclass=type): pass print(Bar,Bar.__class__)#print(Foo,Foo.__class__)#
尽管Foo也指定了元类(submetaclass)type,可是其父类的元类(basemetaclass)是MyType,MyType
是type的子类,因此Foo的元类抛弃了指定的(submetaclass)type,而是沿用了其父类的MyType。
当submetaclass和basemetaclass没有继承关系的时候,将会元类冲突
classMyType(type): pass classMyOtherType(type): pass classBar(object,metaclass=MyType): pass classFoo(Bar,metaclass=MyOtherType): pass
运行代码,当定义的时候就会出现TypeError:metaclassconflict:themetaclassofaderivedclassmustbea(non-strict)元类冲突的错误。
修改代码如下:
classMyType(type): pass classMyOtherType(MyType): pass classBar(object,metaclass=MyType): pass classFoo(Bar,metaclass=MyOtherType): pass print(Bar,Bar.__class__)#print(Foo,Foo.__class__)#
可以看到Bar和Foo分别有自己的元类,并且都符合继承关系中寻找。再调换一下元类看看:
classMyType(type): pass classMyOtherType(MyType): pass classBar(object,metaclass=MyOtherType): pass classFoo(Bar,metaclass=MyType): pass print(Bar,Bar.__class__)#print(Foo,Foo.__class__)#
都使用了Foo还是使用了元子类作为元类。究其原因,其实也很好理解。定义父类的时候,使用了元类MyOtherType。定义子类的时候,通过继承,找到了创建父类的元类,那么父类就是MyOtherType的实例。
如果使用MyType做为元类,那么他就是MyType的实例,MyType的实例会比MyOtherType具有的属性少,那么在继承链上,它又是Bar的子类,这样看就是子类比父类还狭窄了,显然不是一个好的关系。即变成了下面的关系
Bar <- MyOtherType
| ↑
| |
↓ |Foo <- MyType
因此当MyType是MyOtherType的父类的时候,即使Foo指定了MyType作为元类,还是会被忽略,使用其父元类MyOtherType。
上面的线的箭头要一直,才能使用各自指定的元类,否则使用箭头指向的那个类作为元类。元类没有继承关系,元类冲突。
对象(类)实例化
目前为止,我们了解了类的定义,即类是如何被元类创建出来的,但是创建的细节尚未涉及。即元类是如何通过实例化创建类的过程。这也是对象创建的过程。
前文介绍了一个对象是通过类创建的,类对象是通过元类创建的。创建类中,会先调用元类的__new__方法,设置其名称,继承关系和属性,返回一个实例。然后再调用实例的__init__方法进行初始化实例对象。
classMyType(type): def__init__(self,*args,**kwargs): print('init',id(self),args,kwargs) def__new__(cls,*args,**kwargs): print('new',id(cls),args,kwargs) instance=super(MyType,cls).__new__(cls,*args,**kwargs) print(id(instance)) returninstance classBar(object,metaclass=MyType): pass
运行代码可以看见输出:
new4323381304('Bar',(
,),{'__module__':'__main__','__qualname__':'Bar'}){}
4323382232
init 4323382232('Bar',(,),{'__module__':'__main__','__qualname__':'Bar'}){}
注意,上面代码仅关注Bar类的创建,即Bar=MyType('Bar',(object,),{})这个定义代码。MyType进行实例化创建Bar的过程中,会先用其__new__方法,后者调用了父类type的__new__方法,并返回了元类的实例,同时调用这个实例的__init__方法,后者对改实例对象进行初始化。这也就是为什么方法名为__init__。
通常我们会在__init__方法初始化一些实例对象的属性如果__new__方法什么也不返回,那么__init__方法是不会被调用的。
instance=super(MyType,cls).__new__(cls,*args,**kwargs),有的地方也喜欢写成type.__new__或者type,前者是python中如何调用父类方法的问题,后者是直接使用type创建类的过程。比较推荐的写法还是使用super调用其父类的方法的方式。
类是元类的对象,普通类创建对象的过程,也是一样。因此,只要重写__new__方法,还可以实现一个类还可以创建另外一个类的实例的魔法。
移花接木
重写__new__方法,让其创建另外一个类的实例。
classBar: def__init__(self,name): self.name=name print('Barinit') defsay(self): print('say:Bar{}'.format(self.name)) classFoo(object): def__init__(self): print('self{}'.format(self)) def__new__(cls,*args,**kwargs): instance=super(Foo,cls).__new__(Bar,*args,**kwargs) print('instance{}'.format(instance)) instance.__init__('aclass') returninstance defsay(self): print('say:Foo') m=Foo() print('m{}'.format(m)) m.say()
输出
instance<__main__.Barobjectat0x104033240>
Barinit
m<__main__.Barobjectat0x104033240>
say:Baraclass
在类Foo中,通过重写__new__返回了一个Bar类的实例对象,然后调用Bar实例的__inti__方法初始化,由于返回了bar实例,因此Foo的实例没有被创建,因此也不会调用它的实例方法__inti__。这样就把移花(Bar)接木(Foo)上了。
也许有人会觉得这样的诡异魔法有什么用呢?实际上,Tornado框架使用了这样的技术实现了一个叫Configurable的工厂类,用于创建不同网络IO下的epoll还是select模型。有兴趣可以参考其实现方式。
元类的应用
讨论了那么多原理的东西,最后肯定是要应用到实际中才有意义。既然类可以被动态的创建,那么很多定义在类的方法,岂不是也可以被动态的创建了呢。这样就省去了很多重复工作,也能实现酷酷的元编程。
元类可以创建单例模式,也可以用来实现ORM,下面介绍的是Django使用元类实现的查找方式。更经典的model定义网上有很多例子,就不再介绍了。下面介绍一个model通过manger管理器实现查询方法的例子
importinspect classQuerySet: defget(self,*args,**kwargs): print('getmethod') returnself deffilter(self,*args,**kwargs): print('filtermethod') returnself classBaseManager: def__init__(self): pass @classmethod deffrom_queryset(cls,queryset_class,class_name=None): ifclass_nameisNone: class_name='%sFrom%s'%(cls.__name__,queryset_class.__name__) class_dict={ '_queryset_class':queryset_class, } class_dict.update(cls._get_queryset_methods(queryset_class)) returntype(class_name,(cls,),class_dict) defget_queryset(self): returnself._queryset_class() @classmethod def_get_queryset_methods(cls,queryset_class): defcreate_method(name,method): defmanager_method(self,*args,**kwargs): returngetattr(self.get_queryset(),name)(*args,**kwargs) manager_method.__name__=method.__name__ manager_method.__doc__=method.__doc__ returnmanager_method new_methods={} forname,methodininspect.getmembers(queryset_class,predicate=inspect.isfunction): ifhasattr(cls,name): continue queryset_only=getattr(method,'queryset_only',None) ifqueryset_onlyor(queryset_onlyisNoneandname.startswith('_')): continue new_methods[name]=create_method(name,method) returnnew_methods classManager(BaseManager.from_queryset(QuerySet)): pass classModelMetaClass(type): def__new__(cls,*args,**kwargs): name,bases,attrs=args attrs['objects']=Manager() returnsuper(ModelMetaClass,cls).__new__(cls,name,bases,attrs) classModel(object,metaclass=ModelMetaClass): pass classUser(Model): pass User.objects.get() User.objects.filter() User.objects.filter().get()
这样model就用使用期管理器Manger下的方法了。通过model的元类ModelMetaClass,定义model的时候,就初始化了一个Manger对象挂载到Model下面,而定义Manger的时候,也通过元类将QuerySet下的查询方法挂载到Manger下了。
总结
Python里一切都是对象,对象都是由类进行创建实例化而来。既然一切是对象,那么类也是对象,而类这种对象又是由一种更高级类创建而来,即所谓的元类。
元类可以创建类,Python默认的元类是type。通过继承type,可以自定义元类,在自定义元类的时候定义或者重载__new__,可以创建该类的实例对象,同时也可以修改类创建对象的行为。类通过__new__创建实例对象,然后调用实例对象的__init__初始化实例对象。
在使用自定义元类的时候,子类的的元类和父类的元类有关系,前者指定的元类必须和父类的元类是一个继承关系上的,否则会出现元类冲突。子类选取元类的取决于指定的元类和父元类的继承关系,子元类若是父元类的子类,则指定的元类为子元类,否则将会被忽略,使用父元类为其元类。
元类是元编程的一种技术手段,常用于实现工厂模式的策略。通过定义元类动态创建类和展开,可以实现很多设计精妙的应用。ORM正式其中一种常用的方法。
以上就是详解pythonmetaclass(元类)的详细内容,更多关于pythonmetaclass(元类)的资料请关注毛票票其它相关文章!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。