• 参悟python元类(又称metaclass)系列实战(一)


    写在前面

    之前在看廖雪峰python系列的教程时,对元类的章节一直头大,总在思考我到底适不适合学习python,咋这么难,尤其是ORM的部分,倍受打击;后来从0到1手撸了一套ORM,才稍微进阶了一点理解。
    这个系列会从元类开始梳理,最后按廖老师的demo撸一个ORM,希望能对大家有所增益。
    本文中提及的“模板”,泛指“类(class)”。
    有误的地方恳请大神指正下。

    先从普通类实例化的过程探究下

    class Persion:
        def __init__(self, name):
            self.name = name
    
    # 把它实例化
    p1 = Persion()
    
    print(type(p1))         # <class '__main__.Persion'>
    # 打印的信息显示p1这个实例是由 Persion 这个模板创建的,那Persion这个模板是谁创建的?
    
    print(type(Persion))    # <class 'type'>,是type创建了一个叫Persion的模板
    # 由此可见,在实例化 p1 了时候,实际上是走了两步,1.用type创建Persion模板; 2.用Persion模板创建实例p1
    

    思考1:对于过程1,是否就意味着可以用type代替class去创建Persion模板?

    def fn(self, name):
        self.name = name
    
    # type 接收3个位置参数,1.名字:Str,2.父类们: tuple,3.绑定的属性(方法):dict
    Persion = type('demo', (object,), dict(__init__=fn))
    
    # 等同于前面通过class声明的写法
    

    思考2:如果能自定义type的子类并用其创建Persion模板,就意味着可以定制创建过程,这种type的子类,就叫做metaclass(元类)

    1. 先看下如何定义一个type的子类

      # 按照默认习惯,metaclass的类名总是以Metaclass结尾,以便清楚地表示这是一个metaclass
      class PersionMetaclass(type):
          # 重写父类type中的new方法
          def __new__(cls, name: str, bases: tuple, attrs: dict):
              """
              类似class中的__init__方法,实例化时被调用
              @cls: 类似__init__中的self,代表自己,这里代指‘类’自己
              @name: 模板的名字
              @bases: 父类的集合
              @attrs: 属性(方法)集
              """
              # 调用type创建class
              Persion = type.__new__(cls, name, bases, attrs)
              return Persion
      
    2. 分析上面的code,不难发现在调type创建class之前,可以加入定制的内容

      # 需求:1.类名首字母必须大写,2.类中必须有文档注释
      class PersionMetaclass(type):
          def __new__(cls, name, bases, attrs):
              
              if not name.istitle():
                  raise TypeError('类名首字母必须大写')
              
              cls.doc = attrs.get('__doc__')
              if cls.doc is None or len(cls.doc.strip()) == 0:
                  raise TypeError('类中必须有文档注释')
              
              return type.__new__(cls, name, bases, attrs)
      
    3. 使用上面的metaclass测试一下类名首字母大写的限制

      # 定义类的时候,需要显示的指出用 PersionMetaclass 来定制类,关键字“metaclass”
      
      class persion(metaclass=PersionMetaclass):
          pass
      
      persion()
      
      Traceback (most recent call last):
        File "testmetaclass.py", line 39, in <module>
          class Persion(metaclass=PersionMetaclass):
        File "testmetaclass.py", line 35, in __new__
          raise TypeError('类中必须有文档注释')
      TypeError: 类中必须有文档注释
      
    4. 再测试下文档注释的限制

      class Persion(metaclass=PersionMetaclass):
          pass
      
      Persion()
      
      Traceback (most recent call last):
        File "testmetaclass.py", line 39, in <module>
          class Persion(metaclass=PersionMetaclass):
        File "testmetaclass.py", line 35, in __new__
          raise TypeError('类中必须有文档注释')
      TypeError: 类中必须有文档注释
      
    5. 符合要求的类

      class Persion(metaclass=PersionMetaclass):
          """文档注释"""
          pass
      
      Persion()
      

    总结

    • 创建元类就是创建type的派生类

    • 重写__new__方法时,4个位置参数分别是 cls, name: Str, bases: tuple, attrs: dict;分别表示class自己,class的名字,class的父类,class中的属性

    • new 方法需要return 一个 type.new(cls, name, bases, attrs),基本是固定的格式

    • 使用元类时,需要特殊指定 metaclass 来定制

    • 元类跟普通类最显著的区别是:普通类实例化时自动调用__init__方法, 元类实例化时自动调用__new__方法

    • 下一章"参悟python元类(又称metaclass)系列实战(二)"

  • 相关阅读:
    MyEclipse中配置Hibernate
    struts2_对Map进行双层迭代
    Hibernate关联关系全集
    CodeIgniter+Smarty配置
    去掉php框架CI默认url中的index.php【整理】
    jquery的show方法是display:block还是display:inline呢?
    Codeigniter中的Error【转】
    去除 inlineblock 空隙终极解决方案
    jquery三级折叠菜单
    css实现页面文字不换行、自动换行、强制换行
  • 原文地址:https://www.cnblogs.com/z417/p/13926361.html
Copyright © 2020-2023  润新知