metaclass一般翻译成“元类 ”,但也有人认为应该更关注元类“超越变形 ”的能力,应该翻译成超越类 之类的。但是个人觉得“元类”比较顺口,其“本元 ”的含义也是实打实存在的,故本文用元类作为metaclass的中文翻译。
metaclass(元类)可以理解成是类的抽象、本元 。类元编程即使用自定义metaclass 获取极强灵活性的一种编程方式。
不过,在多处都有提到的需要注意 的是:如果不是开发框架 ,不要使用元类,除非只是为了寻找乐趣:) 。
什么是本元? 首先还是要搞清楚什么是本元?这个概念很抽象,必须弄清楚。
本元可以理解成一种更高程度的抽象。首先,类(class)是对象(object)的抽象,对象是类的实例。因此可以说类是对象的本元。而在python中,所有类都是type
类的实例,也就是说type
是所有类的抽象,所以你可以理解type
就是那个元类 。
type 一般我们会常用type(obj)
来获取obj
对象的类型。但type
也可以用来创建新类型 。这时需要传入三个参数:类名 、父类tuple 、属性字典 。
class MyClass: ...
等价于MyClass = type(classname, superclasses, attributedict)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 class SuperCls : def __init__ (self ): print ("__init__ in super class." ) class A (SuperCls ): pass B = type ("B" , (SuperCls, ), {}) a = A() """ __init__ in super class. """ b = B() """ __init__ in super class. """ print (A.mro())""" [<class '__main__.A'>, <class '__main__.SuperCls'>, <class 'object'>] """ print (B.mro())""" [<class '__main__.B'>, <class '__main__.SuperCls'>, <class 'object'>] """
上面类对象的mro()
方法代表Method Resolution Order,即方法解析顺序,可以看到这个类的继承顺序。
通过上面的代码可以看出,使用type
来动态创建类和你自己写代码创建一个类是完全一样 的。
还有个问题,当我们使用type
的时候,它自己是个type
类还是type
对象?如果你直接在解释器中输入print(type)
会得到返回<class 'type'>
,因此可以认为你使用的就是type
类。
OK,搞清楚了什么是“本元”和type
,我们再来看metaclass
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 class MyMeta (type ): def __call__ (self, *args, **kwargs ): print ("__call__ in MyMeta" ) return super ().__call__(*args, **kwargs) def __init__ (cls, what, bases=None , dict =None ): print ("__init__ in MyMeta" ) super ().__init__(what, bases, dict ) def __new__ (*args, **kwargs ): print ("__new__ in MyMeta" ) return type .__new__(*args, **kwargs) class SuperCls : pass class MyClass (SuperCls, metaclass=MyMeta): pass """ __new__ in MyMeta __init__ in MyMeta """ MyClass = MyMeta("MyClass" , (SuperCls, ), {}) """ __new__ in MyMeta __init__ in MyMeta """ my_obj = MyClass() """ __call__ in MyMeta """
看,我们的元类继承自type
类。你在使用这个元类去定义一个新的类时 ,会调用元类的__new__
和__init__
;当你用这个新类去实例化一个新对象时 又会调用元类的__new__
。
因此,你可以可以重载type
的这三个方法来修改type
的行为,但要理清这几个方法被调用的时机 :
1 2 3 __init__(cls, what, bases=None , dict =None ) __new__(*args, **kwargs) __call__(self , *args, **kwargs)
典型案例 来看几个类元编程的典型案例。
Singleton单例模式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class SingletonMeta (type ): instance = None def __call__ (cls, *args, **kwargs ): if cls.instance is None : cls.instance = super (SingletonMeta, cls).__call__(*args, **kwargs) return cls.instance class CurrentUser (object , metaclass=SingletonMeta): def __init__ (self, name=None ): super (CurrentUser, self ).__init__() self .name = name def __str__ (self ): return repr (self ) + ":" + repr (self .name) if __name__ == '__main__' : u = CurrentUser("liu" ) print (u) u2 = CurrentUser() u2.name = "xin" print (u2) print (u) assert u is u2
注:代码来自 https://github.com/Meteorix/python-design-patterns 。
这里重载的是type
的__call__
方法。改变了用户类实例化的行为 ,使其只能得到唯一的实例。
简易ORM框架 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 class Field (object ): def __init__ (self, name, column_type ): self .name = name self .column_type = column_type def __str__ (self ): return '<%s:%s>' % (self .__class__.__name__, self .name) class StringField (Field ): def __init__ (self, name ): super ().__init__(name, 'varchar(100)' ) class IntegerField (Field ): def __init__ (self, name ): super ().__init__(name, 'bigint' ) class ModelMetaclass (type ): def __new__ (cls, name, bases, attrs ): 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 mapping: %s ==> %s' % (k, v)) mappings[k] = v for k in mappings.keys(): attrs.pop(k) attrs['__mappings__' ] = mappings attrs['__table__' ] = name return type .__new__(cls, name, bases, attrs) class Model (dict , metaclass=ModelMetaclass): def __init__ (self, **kw ): super (Model, self ).__init__(**kw) def __getattr__ (self, key ): try : return self [key] except KeyError: raise AttributeError(r"'Model' object has no attribute '%s'" % 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)) class User (Model ): id = IntegerField('id' ) name = StringField('username' ) email = StringField('email' ) password = StringField('password' ) u = User(id =12345 , name='Michael' , email='test@orm.org' , password='my-pwd' ) u.save() """ Found model: User Found mapping: id ==> <IntegerField:id> Found mapping: name ==> <StringField:username> Found mapping: email ==> <StringField:email> Found mapping: password ==> <StringField:password> SQL: insert into User (id,username,email,password) values (?,?,?,?) ARGS: [12345, 'Michael', 'test@orm.org', 'my-pwd'] """
注:代码来自 https://www.liaoxuefeng.com/wiki/1016959663602400/1017592449371072
这里重载的是type
的 __new__
方法,因此改变的是用户类在定义时的行为 。这里用户类是一个数据模型,元类编程做的是在用户定义一个数据模型类的时候,把所有是Field
类型的属性收集起来,统一放到mapping
属性中。这样基类的方法save
可以直接通过mapping
属性来保存需要保存的Field
属性们。
YAMLObject 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 class YAMLObjectMetaclass (type ): def __init__ (cls, name, bases, kwds ): super (YAMLObjectMetaclass, cls).__init__(name, bases, kwds) if 'yaml_tag' in kwds and kwds['yaml_tag' ] is not None : cls.yaml_loader.add_constructor(cls.yaml_tag, cls.from_yaml) class YAMLObject (metaclass=YAMLObjectMetaclass): yaml_loader = Loader class Monster (yaml.YAMLObject): yaml_tag = u'!Monster' def __init__ (self, name, hp, ac, attacks ): self .name = name self .hp = hp self .ac = ac self .attacks = attacks def __repr__ (self ): return "%s(name=%r, hp=%r, ac=%r, attacks=%r)" % ( self .__class__.__name__, self .name, self .hp, self .ac, self .attacks) yaml.load(""" --- !Monster name: Cave spider hp: [2,6] # 2d6 ac: 16 attacks: [BITE, HURT] """ )Monster(name='Cave spider' , hp=[2 , 6 ], ac=16 , attacks=['BITE' , 'HURT' ]) print yaml.dump(Monster( name='Cave lizard' , hp=[3 ,6 ], ac=16 , attacks=['BITE' ,'HURT' ])) """ !Monster ac: 16 attacks: [BITE, HURT] hp: [3, 6] name: Cave lizard """
这里重载的是type
的 __init__
方法,因此改变的是用户类在定义时的行为 。在定义了用户类Monster
时,便通过cls.yaml_loader.add_constructor(cls.yaml_tag, cls.from_yaml)
将Monster
类注册到系统。
PyText 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 class ComponentMeta (type ): def __new__ (metacls, typename, bases, namespace ): if "Config" not in namespace: parent_config = next ( (base.Config for base in bases if hasattr (base, "Config" )), None ) if parent_config is not None : class Config (parent_config ): pass else : class Config (ConfigBase ): pass namespace["Config" ] = Config component_type = next ( ( base.__COMPONENT_TYPE__ for base in bases if hasattr (base, "__COMPONENT_TYPE__" ) ), namespace.get("__COMPONENT_TYPE__" ), ) new_cls = super ().__new__(metacls, typename, bases, namespace) new_cls.Config.__COMPONENT_TYPE__ = component_type new_cls.Config.__name__ = f"{typename} .Config" new_cls.Config.__COMPONENT__ = new_cls new_cls.Config.__EXPANSIBLE__ = namespace.get("__EXPANSIBLE__" ) if component_type: Registry.add(component_type, new_cls, new_cls.Config) return new_cls
注:代码来自 https://github.com/facebookresearch/pytext/blob/351a060b479cbfe1db46b74f1fc975a5b6523122/pytext/config/component.py#L87
这里重载的是type
的 __new__
方法,因此改变的是用户类在定义时的行为 。在定义了用户组件类时,便通过 Registry.add(component_type, new_cls, new_cls.Config)
将该组件类注册到系统。
参考资料