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__)
塵墨 提供的參考代碼

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM