metaclass 的超越變形特性有什麼用? 來看yaml的實例: import yaml class Monster(yaml.YAMLObject): yaml_tag = u'!Monster' def __init__(self, name, hp, ac, attacks): self.n ...
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__)塵墨 提供的參考代碼