如果你见过更好的Python开源框架,那你一定见过元类。例如,在类中定义类属性__metaclass__表示该类是使用元类创建的。元类的实现原理是什么?使用元类可以帮助我们在开发中解决什么样的问题?在这篇文章中,让我们来看看Python元类的来龙去脉。什么是元类?我们都知道定义一个类,然后调用它的构造函数,可以初始化一个实例,像这样:classPerson(object)def__init__(name):self.name=namep=Person('zhangsan')你有没有想过如何我们通常定义的类是如何创建的?别着急,我们先来看一个例子:>>>a=1#创建一个类是inta或者int的实例>>>a.__class__>>>b='abc'#CreatebTheclassofstrbisaninstanceofstr>>>b.__class__>>>defc():#创建c的类是functionmethodcisaninstanceoffunction...pass>>>c.__class__>>>classD(object):#创建d的类Dd是D的一个实例...pass>>>d.__class__在这个例子中,我们定义了Int、str、function、class,然后分别调用它们的__class__方法,这个__class__方法可以返回实例是如何创建的。从方法返回的结果可以看出:创建整数a的类是int,即a是int的实例创建字符串b的类是str,即b是str的实例创建函数c的类是function,也就是说c是function的一个实例,创建实例d的类是class,也就是说d是class的一个实例。除了这些,我们在开发中使用的list和dict也类似,大家可以Test看看结果。既然知道了创建这些实例的类有int、str、function、class,那我们再进一步思考一下,这些类是怎么创建的呢?同样,我们也调用这些类的__class__方法,观察结果:>>>a=1>>>a.__class__.__class__>>>>>>>b='abc'>>>b.__class__.__class__>>>>>>defc():...pass>>>c.__class__.__class__>>>>>>classD(对象):...pass>>>d=D()>>>d.__class__.__class__从结果可以看出,创建这些类的类都是type,所以type就是创建所有类“元类”。换句话说,元类的作用是创建类。可以这样理解:Metaclass->ClassClass->Instance用伪代码表示,如下:klass=MetaClass()#MetaclassCreateClassobj=klass()#创建一个类的实例有意思吗?在这里,你也能感受到这句话的意思:Python中的一切都是对象!无论是普通的类型、方法、实例还是类,都可以看作是对象,它们的起源都是元类。事实上,在Python中,我们可以使用type方法创建一个类。type方法的语法如下:type(class_name,(base_class,...),{attr_key:attr_value,...})比如像下面这样,我们使用type方法创建了MyClass类并且让它继承对象:>>>A=type('MyClass',(object,),{})#type创建一个继承对象的类>>>A>>>A()<__main__.MyClassobjectat0x10d905950>我们还可以使用类型创建一个具有属性和方法的类:>>>deffoo(self):...return'foo'...>>>name='zhangsan'>>>#typecreateB类继承对象包含name属性和foo方法>>>B=type('MyClass',(object,),{'name':name,'foo':foo})>>>B.name#打印name属性'zhangsan'>>>printB().foo()#调用foo方法foo通过type方法创建的类,和我们自己定义一个类,在使用上没有什么区别。其实除了使用type方法创建类之外,我们还可以使用类属性__metaclass__来创建类,这就是下面要讲的“自定义元类”。自定义元类我们可以通过class属性__metaclass__将一个类的创建过程转移到其他地方,可以这样写:classA(object):__metaclass__=...#创建这个类转移到其他地方pass这个例子中,我们首先定义了类A,然后定义了一个类属性__metaclass__,表示创建类A的过程,并传递到其他地方进行处理。那么,这个类属性__metaclass__应该怎么写呢?事实上,它可以是一个方法,也可以是一个类。创建带有方法的类如果类属性__metaclass__分配了一个方法,那么创建类的过程就交给了一个方法来执行。defcreate_class(name,bases,attr):print'createclassbymethod...'#什么都不做就创建一个有类型的类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):print'createclassbyB...'returntype(name,bases,attr)classA(object):#创建类过程交给B__metaclass__=B#Output:#createclassbyB...本例中我们定义了类B,然后将其赋值给A的类变量__metaclass__,也就是说创建A的过程交给了ClassB、定义B时,首先继承type,然后定义__new__方法,最后调用type方法返回一个类,这样创建A类时,会自动调用B类的__new__方法,然后获取类实例。创建类的过程是好的。上面我们演示了两种通过元类创建类的方法,即通过方法创建和通过类创建。其实创建一个类的完整过程是这样的:检查类中是否有__metaclass__属性,如果有则调用__metaclass__指定的方法或者创建一个类。如果类中没有__metaclass__属性,则继续在父类中查找。如果类中没有,则使用类型创建此类。也就是说,如果我们不指定__metaclass__,那么所有的类都是默认按类型创建的。这是我们大多数人定义类的过程。如果在类中指定了__metaclass__,那么这个类的创建就会交给外部,外部可以定义具体的创建逻辑。哪种创建类的方式更好?虽然有两种创建类的方法,但哪种方法更好呢?一般我们推荐使用类来创建,其优点如下:使用类可以更清晰的表达意图使用类更OOP,因为类可以继承其他类,可以更友好的使用面向对象的特性使用类可以更好的组织代码结构另外,在使用类创建类的时候,这里有一个优化点:在__new__方法中,不建议直接调用类型方法,而是调用super的__new__来创建类,执行结果与类型方法相同: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='zhangsan'classB(object):#通过Meta创建类__metaclass__=Metaname='lisi'#打印类属性会自动变成大写,创建类的过程交给了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类时,它的逻辑就交给了另一个类。这个类对类属性进行转换,最终成为对象与数据表的映射。通过转换映射,我们就可以通过实例属性的方式友好的访问到表中对应的字段值。综上所述,本文我们讲了元类的实现原理,了解到元类是创建所有类的根本。我们可以使用类型方法或者在类中定义__metaclass__,将创建类的过程交给外部。当使用__metaclass__创建类时,它可以是方法或类。我们通常使用一个类来实现一个元类,这样可以方便我们组织代码和实现面向对象。在使用元类创建类的时候,我们可以修改创建类的细节,比如统一转换属性,或者增加新的方法等。这对于我们开发功能复杂的类来说非常友好,可以将创建的类进行转换class的细节都被元类屏蔽了,所以在好的开源框架中往往会用到元类。,