当前位置: 首页 > 后端技术 > Python

Python的Metaclass魔法

时间:2023-03-26 11:10:15 Python

通过类型创建类众所周知,在Python编程中,通过类来定义类,然后通过类实例化生成实例对象,所有实例对象都继承自对象对象。但实际上class本身也是一个对象,我们称之为类对象,所有的类对象都继承自type。我们使用以下简单代码在Python交互式CLI中进行测试:#defineclassA>>>classA(object):>>>pass>>>type(A)>>>type(A())>>>isinstance(A,type)True>>>isinstance(A(),object)True我们可以通过类(class)来自定义实例对象(instanceobject)的设计,控制实例对象的创建过程,那么是否可以通过类型来控制类对象(类对象)的创建过程,从而自定义设计类对象的创建过程呢?答案是可以确定的。type本身也是一个类(class),它不仅可以判断一个对象的类型,还可以创建类对象。type(obj):判断一个对象的类型type(name,bases,attrs):创建一个新的类对象。三个参数说明如下:name:类名,str类型bases:该类集成的父类集合,tuple类型attrs:类的属性列表,dict类型创建类type示例:#定义一个方法defgreeting(self,name='world'):print("Hello,%s."%name)#通过type=type("Hello",(object,),{"greeting":greeting})h=Hello()h.greeting()#>>Hello,world.print(type(Hello))#>>print(type(h))#>>元类不仅可以通过类型动态创建类,而且还可以通过继承类型创建一个元类元类,这个元类可以作为其他类的元类。Class是实例对象(instance)的模板,Metaclass是类的模板。三者的关系如下图所示。+---------++---------++---------+|||||||||的实例|||的实例实例+------------>+类+------------>+元类|||||||||||||+----------++----------++----------+这样,在创建类对象之前,Python会先execute使用Metaclass的相关方法来自定义类对象,从而达到动态自定义类的目的。Django中的ORM框架就是这样实现的。这种定制包括给类添加属性和方法,对类进行二次加工。下面演示通过Metaclass为自定义列表类添加insert_before方法的过程。类ListMetaclass(类型):def__new__(mcs,name,bases,attrs):attrs["insert_before"]=lambdaself,item:self.insert(0,item)返回类型.__new__(mcs,name,bases,attrs)classNewList(list,metaclass=ListMetaclass):passnew_list=NewList(["a","b"])new_list.insert_before("insert_before")print(new_list)#>>['insert_before','a','b']首先定义元类ListMetaclass。类的__new__方法用于创建实例对象,Metaclass类似的__new__方法用于创建类对象。在创建类对象之前,我们在attrs中增加insert_before属性,使创建的类对象具有该属性。定义NewList类并指定元类。当NewList创建一个类实例时,它会调用ListMetaclass来创建这样一个实例。注:Metaclass.__new__的参数说明参见上述类型的参数说明。通过元类实现ORM让我们看一个更实际的例子。以Django教程中ORM的使用为例,通过Metaclass开发一个简单的ORM框架。Metaclass查找顺序:创建类时,首先检查类本身是否设置了Metaclass,如果设置了,则直接调用Metaclass创建类;如果没有,则按照基本顺序一路寻找基类,如果找到,则调用父类Metaclass创建一个类;如果没有找到,调用type来创建一个类。在DjangoTutorial中使用ORM主要包括三个步骤:定义模型类,创建模型对象,将模型数据保存到数据库,示例代码如下:#definemodelclassclassQuestion(models.Model):question_text=models.CharField(max_length=200)pub_date=models.DateTimeField('datepublished')#创建模型对象q=Question(question_text="What'snew?",pub_date=timezone.now())#将模型数据保存到数据库q.save()ORM框架的基本设计思想包括以下几点:定义Model类作为所有模型的基类,所有模型对象都继承这个类。定义Field类作为模型所有字段的基类,模型的属性(Field字段)以类变量的形式定义在Model中。定义ModelMetaclass类作为Model的Metaclass,解析Model中的Field字段,进行预处理,保存为Model的元数据,用于ORM映射。实现Model中的save方法,利用Model的元数据自动组装InsertSQL语言。先搭建一个框架,再填写各个部分的功能。fromdatetimeimportdatetimeclassModelMetaClass(type):def__new__(mcs,name,bases,attrs):返回类型.__new__(mcs,name,bases,attrs)classModel(metaclass=ModelMetaClass):def__init__(self,**kwargs):传递def__setattr__(self,key,value):传递def__getattr__(self,item):传递defsave(self):传递类Field:传递类CharField(Field):传递类DateTimeField(Field):传递类Question(Model):question_text=CharField()pub_date=DateTimeField()question=Question(question_text="我的第一个问题.",pub_data=datetime.now())question.save()按照设计思路,功能改进代码如下:ModelMetaclass主要解析Model中的Field类型字段,生成ORM数据库表字段的元数据。fromdatetimeimportdatetimeclassModelMetaclass(type):def__new__(mcs,name,bases,attrs):#如果name=="Model"orModelnotinbases,则只处理Model的子类:returntype.__new__(mcs,name,bases,attrs)#ProcessField类型字段,信息存储在__fields__字段中fields=dict()forkeyinattrs:ifisinstance(attrs[key],Field):fields[key]=attrs[key]attrs["__fields__"]=fields#表名默认为类名的小写attrs["__table__"]=name.lower()#删除Field类型的类变量forkeyinfields:attrs.pop(key)returntype.__new__(mcs,name,bases,attrs)模型类作为所有ORM模型的基类,为模型提供通用的功能。__init__是模型构造函数,处理传入的Field字段__setattr__、__getattr__使得模型Field类型的字段可以像普通字段一样进行设置和赋值。save方法用于生成将模型保存到数据库的SQL语句。classModel(metaclass=ModelMetaclass):def__init__(self,**kwargs):#Field字段数据存储在self.fieldsself.fields=dict()#self.__fields__是Metaclass中的attrs["__fields__"]字段model_fields=self.__fields__forkwarg_keyinkwargs:ifkwarg_keyinmodel_fields:self.fields[kwarg_key]=kwargs[kwarg_key]else:raiseKeyError()def__setattr__(self,key,value):#由model.field=xxx实现forField字段赋值ifkeyinself.__fields__:self.__dict__["fields"][key]=valuereturnself.__dict__[key]=valuedef__getattr__(self,key):#实现通过model读取Field字段值。如果在self.__fields__中输入字段:returnself.__dict__["fields"][key]returnself.__dict__[key]defsave(self):model_fields=self.__fields__fields_key=list()fields_value=list()fields_placeholder=list()forfield_keyinmodel_fields:fields_key.append(field_key)fields_value.append(self.fields.setdefault(field_key,None))fields_placeholder.append("?")sql="INSERTINTO%s(%s)VALUES(%s)"%(self.__table__,",".join(fields_key),",".join(fields_placeholder))print("SQL:",sql)print("ARGS:",fields_value)Field类及其子类、模型类Field:passclassCharField(Field):passclassDateTimeField(Field):passclassQuestion(Model):question_text=CharField()pub_date=DateTimeField()ORM使用示例、执行输出question=Question(question_text="我的第一个问题。",pub_date=datetime.now())question.save()#>>SQL:INSERTINTOquestion(question_text,pub_date)VALUES(?,?)#>>ARGS:['我的第一个问题。',datetime.datetime(2020,10,13,17,52,38,443969)]question.question_text="我的第二个问题。"question.save()#>>SQL:INSERTINTOquestion(question_text,pub_date)VALUES(?,?)#>>ARGS:['我的第二个问题。',datetime.datetime(2020,10,13,17,52,38,443969)]从可用的打印输出可以看出,框架已经打印出了SQL语句和参数,只要提交给数据库,就可以实际运行。结语通过以上简单的例子,相信大家应该对Metaclass的使用有了更深入的了解,但是要看懂代码,最好还是自己编码调试。得益于Python动态语言特性的灵活性和巧妙设计,我们用不到100行代码实现了一个ORM框架原型,各方面都优于Java等重量级编译型语言。但这只是一个简单的例子,实际的框架还有很多工作要做。参考:什么是Python中的元类?一篇使用元类的文章,让你全面了解__getattr__、__getattribute__、__getitem__的使用和执行原理