• 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__)
    尘墨 提供的参考代码
     
  • 相关阅读:
    Django models通过DateTimeField保存到MySQL的时间的时区问题
    apache静态文件配置
    Python多线程学习
    Django filter中用contains 在mysql中的问题
    python:open/文件操作
    C++primer plus第六版课后编程题答案
    C++primer plus第六版课后编程题答案8.3
    C++primer plus第六版课后编程题答案8.2
    C++primer plus第六版课后编程题答案8.1
    C++primer plus第六版课后编程题答案7.10
  • 原文地址:https://www.cnblogs.com/xiaoguanqiu/p/11074603.html
Copyright © 2020-2023  润新知