Python进阶:metaclass谈
metaclass 的超越变形特性有什么用?
来看yaml的实例:
import yaml 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) monster1 = yaml.load(""" --- !Monster name: Cave spider hp: [2,6] # 2d6 ac: 16 attacks: [BITE, HURT] """,Loader=yaml.Loader) print(monster1) #Monster(name='Cave spider', hp=[2, 6], ac=16, attacks=['BITE', 'HURT']) print(type(monster1)) #<class '__main__.Monster'> print (yaml.dump(Monster( name='Cave lizard', hp=[3,6], ac=16, attacks=['BITE','HURT'])) ) # dump() 返回 str # 输出 # !Monster # ac: 16 # attacks: [BITE, HURT] # hp: [3, 6] # name: Cave lizard
上面的代码调用yaml.load(),就能把任意一个 yaml 序列载入成一个 Python Object;而调用yaml.dump(),就能把一个 YAMLObject 子类序列化。对于 load() 和 dump() 的使用者来说,他们完全不需要提前知道任何类型信息,这让超动态配置编程成了可能。
只要简单地继承 yaml.YAMLObject,就能让你的 Python Object 具有序列化和逆序列化能力。
metaclass 的超越变形特性怎么用?
YAML 怎样用 metaclass 实现动态序列化 / 逆序列化功能,看其源码
#Python 2/3 相同部分 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) # 省略其余定义 # Python 3 class YAMLObject(metaclass=YAMLObjectMetaclass): yaml_loader = Loader # 省略其余定义 # Python 2 class YAMLObject(object): __metaclass__ = YAMLObjectMetaclass yaml_loader = Loader # 省略其余定义
YAMLObject 把 metaclass 都声明成了 YAMLObjectMetaclass
在你定义任何 YAMLObject 子类时,Python 会强行插入运行下面这段代码
cls.yaml_loader.add_constructor(cls.yaml_tag, cls.from_yaml)
Python 底层语言设计层面是如何实现 metaclass 的?
第一,所有的 Python 的用户定义类,都是 type 这个类的实例。
class MyClass: pass instance = MyClass() print(type(instance)) # 输出 #<class '__main__.MyClass'> print(type(MyClass)) # 输出 #<class 'type'>
instance 是 MyClass 的实例,而 MyClass 不过是“上帝”type 的实例。
第二,用户自定义类,只不过是 type 类的__call__运算符重载。
class MyClass: data = 1 instance = MyClass() print(MyClass, instance) # 输出 #(__main__.MyClass, <__main__.MyClass instance at 0x7fe4f0b00ab8>) print(instance.data) # 输出 #1 MyClass = type('MyClass', (), {'data': 1}) instance = MyClass() print(MyClass, instance) # 输出 #(__main__.MyClass, <__main__.MyClass at 0x7fe4f0aea5d0>) print(instance.data) # 输出 #1
可以看出,定义Myclass的时候Python实际调用的是type(classname, superclasses, attributedict),就是 type 的__call__运算符重载,接着会进一步调用
type.__new__(typeclass, classname, superclasses, attributedict) type.__init__(class, classname, superclasses, attributedict)
第三,metaclass 是 type 的子类,通过替换 type 的__call__运算符重载机制,“超越变形”正常的类。
一旦你把一个类型 MyClass 的 metaclass 设置成 MyMeta,MyClass 就不再由原生的 type 创建,而是会调用 MyMeta 的__call__运算符重载。
class = type(classname, superclasses, attributedict) # 变为了 class = MyMeta(classname, superclasses, attributedict)
使用 metaclass 的风险
正如你所看到的那样,metaclass 会"扭曲变形"正常的 Python 类型模型。所以,如果使用不慎,对于整个代码库造成的风险是不可估量的。换句话说,metaclass 仅仅是给小部分 Python 开发者,在开发框架层面的 Python 库时使用的。而在应用层,metaclass 往往不是很好的选择。
参考
极客时间《Python 核心技术与实战》
class Mymeta(type): def __init__(self, name, bases, dic): super().__init__(name, bases, dic) print('===>Mymeta.__init__') print(self.__name__) print(dic) print(self.yaml_tag) def __new__(cls, *args, **kwargs): print('===>Mymeta.__new__') print(cls.__name__) return type.__new__(cls, *args, **kwargs) def __call__(cls, *args, **kwargs): print('===>Mymeta.__call__') obj = cls.__new__(cls) obj.testPerporet = 'change' #修改子类的属性 cls.__init__(cls, *args, **kwargs) return obj class Foo(metaclass=Mymeta): yaml_tag = '!Foo' testPerporet = 'orig' def __init__(self, name): print('Foo.__init__') self.name = name def __new__(cls, *args, **kwargs): print('Foo.__new__') return object.__new__(cls) foo = Foo('foo') print(foo.__dict__)