metaclass很少被用到,以至于学习Python时你根本可以先不用管它。最近跟着廖雪峰大神编写orm,所以来看看metaclass的用法。
metaclass的作用
metaclass是指定类由谁创建的,它能够定制类的创建过程。
object和type是python中的两个源对象,python的新式类中都是继承自object类,而<class ‘object’>的子类的类型都是<class ‘type’>。具体object和type的关系可以见这篇文章Python中的type和object【转载】。
因此,当我们创建一个类时:
class Test(object):
pass
其实,在python解释器中,上述语句被解释成:
Test=type('Test',(object,),{})
三个参数分别为:类名;类的继承关系;类的字段,方法等。
因此,我们可以使用type来创建一个类。
class Father(object):
pass
class Man(object):
pass
s=type('Child',(Father,Man,),{'speak':'hello, how are you.'})
print(s.speak)
print(s.__dict__)
print(s.__bases__)
print(s.__class__)
运行结果为:
hello, how are you.
{'speak': 'hello, how are you.', '__module__': '__main__', '__doc__': None}
(<class '__main__.Father'>, <class '__main__.Man'>)
<class 'type'>
如果我们想要自定义类的创建过程,我们可以继承type。
自定义类的创建
# -*- coding:utf-8 -*-
class Field(object):
def __init__(self,name,column_type):
self.name=name
self.column_type=column_type
#当使用print时,python会调用它的str方法
#两个参数为:类的名字,实例的name参数
def __str__(self):
return "<%s:%s>" %(self.__class__.__name__,self.name)
#定义各种类型的Field
class StringField(Field):
def __init__(self,name):
#调用父类的init函数
super(StringField,self).__init__(name,"varchar(100)")
class IntegerField(Field):
def __init__(self,name):
super(IntegerField,self).__init__(name,"bigint")
#编写ModelMetaclass
class ModelMetaclass(type):
#__new__方法的参数
#准备创建的类的对象, cls
#类的名字,name
#类继承的父类集合,bases
#类的属性的集合,attrs
def __new__(cls,name,bases,attrs):
#若新建的类名为Model,则不做修改
if name=='Model':
return type.__new__(cls,name,bases,attrs)
print("Found model:%s"% name)
mappings=dict()
for k,v in attrs.items():
if isinstance(v,Field):
print("Found mappings:%s : %s"%(k,v))
mappings[k]=v
#将映射从方法集合attrs中挑出,组成__mappings__
#而后把__mappings__添加到attrs中
for k in mappings.keys():
attrs.pop(k)
attrs["__mappings__"]=mappings
attrs["__table__"]=name #添加表名
return type.__new__(cls,name,bases,attrs)
#编写Model基类
#继承dict
#指定metaclass,能够定制类的创建过程
class Model(dict,metaclass=ModelMetaclass):
def __init__(self,**kw):
#调用dict的初始化方法
super(Model,self).__init__(**kw)
#令可以用d.k获取key的值
def __getattr__(self,key):
try:
return self[key]
except KeyError:
raise AttributeError(r"'Model' object has no attribute '%s'"%key)
#可以使用d.k动态设置key的值
def __setattr__(self,key,value):
self[key]=value
def save(self):
fields=[]
params=[]
args=[]
#在所有映射中迭代
for k, v in self.__mappings__.items():
fields.append(v.name)
params.append("?")
args.append(getattr(self,k,None))
sql="insert into %s (%s) values (%s)" %(self.__table__,",".join(fields),",".join(params))
print("SQL:%s" %sql)
print("ARGS: %s" % str(args))
#定义User类,对应了数据库中的表User
class User(Model):
#定义类的属性到列的映射
id=IntegerField("id")
name=StringField("username")
password=StringField("password")
u=User(id=123,name="dxk",password="123456")
u.save()
print(u.__mappings__)
print(u.__table__)
运行结果为:
Found model:User
Found mappings:id : <IntegerField:id>
Found mappings:name : <StringField:username>
Found mappings:password : <StringField:password>
SQL:insert into User (id,username,password) values (?,?,?)
ARGS: [123, 'dxk', '123456']
{'id': <__main__.IntegerField object at 0x00000013DB505390>, 'name': <__main__.S
tringField object at 0x00000013DB5053C8>, 'password': <__main__.StringField obje
ct at 0x00000013DB505400>}
User
有关__new__的简单介绍可以看:Python中__new__和__init__的简单介绍
上述代码的执行流程:
解释器执行至class Model时,得知其由ModelMetaclass创建。解释完类内的方法后,进入ModelMetaclass解释__new__方法,由于没有__init__,执行type中的__init__。此时__new__中的cls为Model。
解释器执行到class User后,解释完定义的字段后,执行ModelMetaclass的__new__方法。此时cls为User,name为‘User’,base为Model,attrs为包含字段的字典。
可以看到,在ModelMetaclass中,我们对相应类中的属性attrs进行操作,比如User类中的id,name等。我们使用元类就是修改一些特性,构造自己需要的属性并且返回类。