classPerson(object)def__init__(name):self.name=namep=Person(zhangsan)那你有没有想过,我们平时定义的类,它是如何创建出来的?别着急,我们先来看一个例子:
a=1#创建a的类是inta是int的实例a.__class__typeintb=abc#创建b的类是strb是str的实例b.__class__typestrdefc():#创建c的类是function方法c是function的实例...passc.__class__typefunctionclassD(object):#创建d的类是Dd是D的实例...passd.__class__class__main__.D在这个例子中,我们定义了int、str、function、class,然后分别调用了它们的__class__方法,这个__class__方法可以返回实例是如何创建出来的。从方法返回的结果我们可以看到:创建整数a的类是int,也就是说a是int的一个实例创建字符串b的类是str,也就是说b是str的一个实例创建函数c的类是function,也就是说c是function的一个实例创建实例d的类是class,也就是说d是class的一个实例除了这些之外,我们在开发中使用到的例如list、dict也类似,你可以测试观察一下结果。现在我们已经得知,创建这些实例的类是int、str、function、class,那进一步思考一下,这些类又是怎么创建出来的呢?同样地,我们也调用这些类的__class__方法,观察结果:
a=1a.__class__.__class__typetypeb=abcb.__class__.__class__typetypedefc():...passc.__class__.__class__typetypeclassD(object):...passd=D()d.__class__.__class__typetype从结果我们可以看到,创建这些类的类,都是type,所以type就是创建所有类的「元类」。也就是说,元类的作用就是用来创建类的。你可以这样理解:元类-类类-实例用伪代码表示,就是下面这样:
klass=MetaClass()#元类创建类obj=klass()#类创建实例是不是很有意思?在这里,你也可以感受一下这句话的含义:Python中一切皆对象!无论是普通类型、方法、实例,还是类,都可以统一看作对象,它们的起源就是元类。其实,在Python中,使用type方法,我们可就以创建出一个类,type方法的语法如下:
type(class_name,(base_class,...),{attr_key:attr_value,...})例如,像下面这样,我们使用type方法创建MyClass类,并且让它继承object:
A=type(MyClass,(object,),{})#type创建一个类,继承objectAclass__main__.MyClassA()__main__.MyClassobjectat0x10d我们还可以使用type创建一个包含属性和方法的类:
deffoo(self):...returnfoo...name=zhangsan#type创建类B继承object包含name属性和foo方法B=type(MyClass,(object,),{name:name,foo:foo})B.name#打印name属性zhangsanprintB().foo()#调用foo方法foo通过type方法创建的类,和我们自己定义一个类,在使用上没有任何区别。其实,除了使用type方法创建一个类之外,我们还可以使用类属性__metaclass__创建一个类,这就是下面要讲的「自定义元类」。自定义元类我们可以使用类属性__metaclass__把一个类的创建过程,转交给其它地方,可以像下面这样写:
classA(object):__metaclass__=...#这个类的创建转交给其他地方pass这个例子中,我们先定义了类A,然后定义了一个类属性__metaclass__,这个属性表示创建类A的过程,转交给其它地方处理。那么,这个类属性__metaclass__需要怎么写呢?其实,它可以是一个方法,也可以是一个类。用方法创建类如果类属性__metaclass__赋值的是一个方法,那么创建类的过程,就交给了一个方法来执行。
defcreate_class(name,bases,attr):printcreateclassbymethod...#什么事都没做直接用type创建了一个类returntype(name,bases,attr)classA(object):#创建类的过程交给了一个方法__metaclass__=create_class#Output:#createclassbymethod...我们定义了create_class方法,然后赋值给__metaclass__,那么类A被创建时,就会调用create_class方法。而create_class方法中的逻辑,就是我们上面所讲到的,使用type方法创建出一个类,然后返回。用类创建类明白了用方法创建类之后,我们来看一下用类来创建另一个类。
classB(type):#必须定义__new__方法返回一个类def__new__(cls,name,bases,attr):printcreateclassbyB...returntype(name,bases,attr)classA(object):#创建类的过程交给了B__metaclass__=B#Output:#createclassbyB...在这个例子中,我们定义了类B,然后把它赋值给了A的类变量__metaclass__,这就表示创建A的过程,交给了类B。B在定义时,首先继承了type,然后定义了__new__方法,最后调用type方法返回了一个类,这样当创建类A时,会自动调用类B的__new__方法,然后得到一个类实例。创建类的过程好了,上面我们演示了通过元类创建一个类的两种方式,分别是通过方法创建和通过类创建。其实创建一个类的完整流程如下:检查类中是否有__metaclass__属性,如果有,则调用__metaclass__指定的方法或类创建如果类中没有__metaclass__属性,那么会继续在父类中寻找如果任何父类中都没有,那么就用type创建这个类也就是说,如果我们没有指定__metaclass__,那么所有的类都是默认由type创建,这种情况是我们大多数定义类时的流程。如果类中指定了__metaclass__,那么这个类的创建就会交给外部来做,外部可以定义具体的创建逻辑。哪种创建类的方式更好?虽然有两种方式可以创建类,那么哪种方式更好呢?一般我们建议使用类的方式创建,它的优点如下:使用类更能清楚地表达意图使用类更加OOP,因为类可以继承其他类,而且可以更友好地使用面向对象特性使用类可以更好地组织代码结构另外,使用类创建一个类时,这里有一个优化点:在__new__方法中不建议直接调用type方法,而是建议调用super的__new__来创建类,执行结果与type方法是一样的:
classB(type):def__new__(cls,name,bases,attr):#使用super.__new__创建类returnsuper(B,cls).__new__(cls,name,bases,attr)创建类时自定义行为前面我们用元类创建一个类时,它的功能非常简单。现在我们来看一下,使用元类创建类时,如何定义一些自己的逻辑,然后改变类的属性或行为。我们看下面这个例子:
#coding:utf8classMeta(type):def__new__(cls,name,bases,attr):#通过Meta创建的类属性会都变成大写fork,vinattr.items():ifnotk.startswith(__):attr[k]=v.upper()else:attr[k]=vreturntype(name,bases,attr)classA(object):#通过Meta创建类__metaclass__=Metaname=zhangsanclassB(object):#通过Meta创建类__metaclass__=Metaname=lisi#打印类属性会自动变成大写printA.name#ZHANGSANprintB.name#LISI在这个例子中,我们定义了一个元类Meta,然后在定义类A和B时,把创建类的过程交给了Meta,在Meta类中,我们可以拿到A和B的属性,然后把它们的属性都转换成了大写。所以当我们打印A和B的属性时,虽然定义的变量是小写的,但输出结果都变成了大写,这就是元类发挥的作用。使用场景了解了元类的实现原理,那么元类都会用在哪些场景呢?我们在开发中其实用的并不多,元类的使用,经常会出现在一些框架中,例如DjangoORM、peewee,下面是使用DjangoORM定义一个数据表映射类的代码:
classPerson(models.Model):#注意:name和age是类属性name=models.CharField(max_length=30)age=models.IntegerField()person=Person(name=zhangsan,age=20)printperson.name#zhangsanprintperson.age#20仔细看在这段代码中,我们定义了一个Person类,然后在类中定义了类属性name和age,它们的类型分别是CharField和IntegerField,之后我们初始化Person实例,然后通过实例获取name和age属性,输出的却是str和int,而不再是CharField和IntegerField。能做到这样的秘密就在于,Person类在创建时,它的逻辑交给了另一个类,这个类针对类属性进行了转换,最终变成对象与数据表的映射,通过转换映射,我们就可以通过实例属性的方式,友好地访问表中对应的字段值了。总结总结一下,这篇文章我们讲了元类的实现原理,了解到元类是创建所有类的根源,我们可以通过type方法,或者在类中定义__metaclass__的方式,把创建类的过程交给外部。当使用__metaclass__创建类时,它可以是一个方法,也可以是一个类。我们通常会使用类的方式去实现一个元类,这样做更方便我们组织代码,实现面向对象。在使用元类创建一个类时,我们可以修改创建类的细节,例如对属性做统一的转换,或者增加新的方法等等,这对于我们开发一个复杂功能的类很友好,它可以把创建类的细节屏蔽在元类中,所以元类常常用在优秀的开源框架中。、
更多阅读
年最佳流行Python库Top10
Python中文社区热门文章Top10
5分钟快速掌握Python定时任务框架
特别推荐
点击下方阅读原文加入社区会员
预览时标签不可点收录于话题#个上一篇下一篇