Python 的 metaclass

news2024/11/15 19:31:04

文章目录

  • 先说结论
      • 1. metaclass 的作用
      • 2. 主要的执行过程
  • 1. `metaclass.__new__`
  • 2. `metaclass.__call__`
      • 关于 `metaclass.__init__`
  • 3. `metaclass.__prepare__`
  • 4. 自动创建 `__slots__` 属性
    • 4.1 metaclass 的接口类
    • 4.2 metaclass conflict
  • 5. Class metaprogramming


先说结论

1. metaclass 的作用

metaclass 的作用是:对新创建的类 new_class 实现一些定制 customization 功能。例如让 new_class 实现自动增加 __slots__ 的功能。
Python 默认的 metaclass 是 type,除了把 type 用于查询类型,type 还可以可以创建 class。而自定义的 metaclass 则继承 type,因此自定义的 metaclass 也能在创建 new_class 时,对 new_class 进行定制。

2. 主要的执行过程

在创建一个新的类 new_class 时,主要的执行过程包括如下 6 步(下面用 new_class 作为新类的名字) :

  1. 解析 MRO。
  2. 确定 metaclass。
  3. 准备好命名空间 namespace。
    3.1 执行 metaclass.__prepare__ 方法,默认返回的一个空字典 namespace_dict 作为命名空间。
    3.2 把 __qualname__ 和 __module__ 放入到 namespace_dict 中。
  4. 执行 new_class 的 body 部分,然后再把 new_class 的所有属性,一起放入到 namespace_dict 中。
  5. 执行 metaclass.__new__ 方法。注意它会被 __init_subclass__ 打断,分成 2 部分代码执行。
    5.1 执行 metaclass.__new__ 中的命令,直到 super().__new__() 为止。
    5.2 执行基类的 __init_subclass__ 方法。
    5.3 然后再执行 super().__new__() 之后的代码。
    5.4 如果 metaclass.__new__ 方法返回了一个 class 对象,会自动执行 metaclass.__init__ 方法。
  6. 将新建的 class 对象和名字 new_class 进行绑定。

对 class 的定制操作,主要发生在上面的第 3、4、5 步骤。
使用 metaclass 时的详细执行过程,可以参看 Python 官网: https://docs.python.org/3/reference/datamodel.html#metaclasses

下面进行详细讨论。


下面的内容涉及到 type()super() 和 __mro__,__slots__,__call__,__init_subclass__ 等。
读者需要先了解它们的基本用法之后,才能方便地阅读本篇的内容。

关于 super() 和 mro,可以参看我的另一篇文章:
《Python 的 super 函数, mro 和多继承》https://blog.csdn.net/drin201312/article/details/137398779


1. metaclass.__new__

在创建新的 class 时,如果用到 metaclass ,则会调用 metaclass.__new__ 方法。

metaclass.__new__ 会有 4 个固定参数 meta_cls, new_cls_name, bases, namespace_dict,以及另外一个关键字参数 kwargs。

下图示例展示了 metaclass.__new__ 的用法,可以查看这 5 个参数的具体内容。

建议把 metaclass.__new__ 的第一个参数写为 meta_cls 或是 mcs 。这是因为, metaclass.__new__ 是一个 static method,它的第一个参数是 metaclass,写成 meta_cls 更能表达其含义。

在这里插入图片描述
运行结果如下图,在 metaclass.__new__ 方法中,注意下面 5 个特点:

  1. meta_cls 是当前的 metaclass,在此例中就是 MyMeta。
  2. new_cls_name 则是即将被创建出来的类 NewCls 。
  3. bases 是元祖,列出所有的基类 。
  4. namespace_dict 是用 class 关键字定义 NewCls 时,所有的属性和方法。
  5. 用 super 创建新类时,必须区分是否有基类的两种情况。
    在这里插入图片描述

2. metaclass.__call__

metaclass 的一个特点是:每次对类 new_class 创建一个 instance,都会执行 metaclass.__call__ 方法。
其中的逻辑如下:

  1. 对一个对象 Foo 使用小括号 () 进行调用时,即执行 Foo() 时,就会执行 Foo.__class__ 的 __call__ 方法。
  2. 如果这个 Foo 是一个 class, Foo.__class__ 就是 Foo 的 metaclass。
  3. 而执行 Foo() 就是在创建 Foo 的 instance。

因此,创建一个 class 的 instance 时,就会执行该 class 对应的 metaclass.__call__ 方法。

下图的 Singleton 就是利用了这个特性,使得只能给 Singleton 创建唯一的一个 instance。该示例参考了第三版 《Python Cookbook》中的 Singleton,进行了一些修改,以便使其更易理解。
在这里插入图片描述

关于 metaclass.__init__

如果 metaclass.__new__ 运行并返回一个 instance ,则会自动调用 metaclass.__init__。除了第一个参数,其它参数和 metaclass.__new__ 一样。并且 metaclass.__init__ 是在基类的 __init_subclass__ 方法之后执行。
但是对于 metaclass 来说,一般可以不使用 metaclass.__init__ 。初始化的工作,可以放在 metaclass.__new__ 中进行,正如上图例子的 SingletonMeta 所示。具体原因是:

  1. metaclass 有 metaclass.__new__ 方法,只要 super().__new__ 返回了新的 class ,就可立刻对这个 class 进行初始化。
  2. 而普通的 class,因为一般不会单独对其创建 __new__ 方法,所以只能把初始化的工作放到 __init__ 中。

3. metaclass.__prepare__

__prepare__ 只对 metaclass 有效。

现在已经不太需要使用 metaclass.__prepare__ 了。原因是:
在 Python 3.6 之前,字典是没有顺序的。metaclass.__prepare__ 中使用 OrderedDict ,使得类属性保持一个固定的顺序,这个顺序就是定义 class 时的顺序。然后 metaclass.__new__ 可以按这个固定的顺序来处理这些属性。
但是 Python 3.6 之后的字典已经是有顺序的了,所以不再需要使用 metaclass.__prepare__ 对属性进行排序。

下图是 David Beazley 在《Python Distilled》中的例子,它展示了 metaclass.__prepare__ 的一种用法:用它可以检查 class 中的属性是否有重名。我对这个例子做了一些修改和说明,以便于理解。
这个示例的原理是:在创建新的 class 时,metaclass.__prepare__ 会返回一个空字典,而新的 class 的所有属性,都要被放入这个字典中。因此可以对这个字典进行定制,用它检查属性是否重名。
在这里插入图片描述
上图的运行结果如下。
在这里插入图片描述


4. 自动创建 __slots__ 属性

可以使用 metaclass 进行一种定制 customization:让新建的 new_class 自动创建 __slots__ 属性。
如果要创建 __slots__ 属性,则必须在生成 class 对象之前,也就是 type.__new__ 之前,否则 __slots__ 无效。
因此,在 metaclass.__new__ 方法中,必须在 super().__new__ 之前就创建好 __slots__
同理,__init_subclass__ 和 class decorator 都无法设置 __slots__ ,因为 __init_subclass__ 和 class decorator 都是在创建好 class 对象之后才起作用的。
自动创建 __slots__ 的示例如下,3 个主要步骤是:

  1. 先获得预先定义好的 __slots__
  2. __init__ 中的参数添加到 __slots__ 中。
  3. 把两个来源的 __slots__ 求并集,放入 namespace_dict 中。
import inspect

class AutoSlotMeta(type): 
    """该 metaclass 的作用是把 __init__ 中的参数,自动添加到 __slots__ 属性中。 """
    def __new__(meta_cls, new_cls_name, bases, namespace_dict, **kwargs):  # noqa
        slots = namespace_dict.pop('__slots__', {})  # 1. 先获得预先定义好的 __slots__。
        # 2. 然后把 __init__ 中的参数添加到 __slots__ 中。
        if '__init__' in namespace_dict:
            # 把 __init__ 方法的参数转换为 Signature 对象。
            sig = inspect.signature(namespace_dict['__init__'])
            # 将 Signature.parameters 转换为一个元祖,只保留参数的名字,去掉参数的默认值。
            slots_from_init = tuple(sig.parameters)[1:]  # 去掉第 0 位的 self 参数。

            slots = set(slots)  # 转换为集合再求并集。
            slots |= set(slots_from_init)  # 3. 把两个来源的 __slots__ 求并集,放入 namespace_dict 中。  
        namespace_dict['__slots__'] = tuple(slots)  # 重新创建 __slots__
        print(f'{new_cls_name= }, {namespace_dict["__slots__"]=}')
        if bases:  # 当有基类时,kwargs 会被传递给基类的 __init_subclass__ 方法。
            new_class = super().__new__(meta_cls, new_cls_name, bases, namespace_dict, **kwargs)
        else:  # 没有基类时,不应该传入 kwargs。因为 type.__init_subclass__ 不接收多余的关键字参数。
            new_class = super().__new__(meta_cls, new_cls_name, bases, namespace_dict)
        return new_class

class Parent:
    __slots__ = ()  # 为了子类的 __slots__ 起作用,基类必须有 __slots__ 属性。
# 在使用 metaclass 的接口类时,如果还要继承基类,则应该用接口类来继承基类,发生 metaclass conflict,如下所示。
class SlotInterface(Parent, metaclass=AutoSlotMeta):
    pass
# 如果有基类,则基类也必须有 __slots__ 属性。否则子类的 __slots__ 不起作用,导致子类可以随意创建属性。
class DemoSlot(SlotInterface):
    __slots__ = 'foo',  # 如果需要把其它属性放入 __slots__,可以预先定义。
    def __init__(self, bar=None):
        pass

使用这个的 AutoSlotMeta 效果如下图。
在这里插入图片描述
这个自动创建 __slots__ 的例子参考了 《Python Distilled》的 SlotMeta,并做了一些改进。
《Fluent Python》中 24-15 例子的 MetaBunch ,以及 Caleb Hattingh 的 autoslot 库,都可以自动创建 __slots__ ,也可以参考。autoslot 库的地址:https://github.com/cjrh/autoslot

在这个例子中,提到了 metaclass 的接口类和 metaclass conflict,下面略作介绍。

4.1 metaclass 的接口类

使用 metaclass 时,一种常见做法是使用接口类。具体做法是:创建 metaclass,然后创建一个普通的 class Interface 作为接口 class ,这个 Interface 则直接使用 metaclass。上面例子的 SlotInterface 就是接口类。
这样做既可以获得 metaclass 的功能,又可以尽量保持接口稳定,并且把 metaclass 作为内部细节 implementation detail,进行了隐藏。
接口稳定是指:用户创建新类时始终继承 Interface ,不需要更改。而开发者如果创建了新的 metaclass,只需要修改 Interface ,让 Interface 使用最新的 metaclass 即可。
另外注意,在使用 metaclass 的接口类时,如果还要继承基类,则应该用接口类来继承基类,即写成 class SlotInterface(Parent, metaclass=AutoSlotMeta) 的形式。

4.2 metaclass conflict

使用 metaclass 时,有时会产生 metaclass conflict 报错: typeError:metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases

如果同时满足下面 3 个条件,就会产生 metaclass conflict:

  1. class 同时设置了基类和 metaclass Foo 。
  2. 基类也有 metaclass,假设为 BaseMeta.
  3. 如果 metaclass Foo 不是 BaseMeta 的子类,则会发生 metaclass conflict。

在 AutoSlotMeta 这个例子中,如果用新类 DemoSlot 直接继承基类 Parent,就会引发报错 metaclass conflict 。


5. Class metaprogramming

最后说一下 metaprogramming 和 class metaprogramming 这两个术语。

  1. metaprogramming 和普通的 programming 相对。普通 programming 编写普通的 class 和 function 等,这些 class 和 function 用于实现直接的功能,比如计算求和,求平均值等功能。

  2. metaprogramming 是指编写一些 meta code,这些 meta code 用于创建或修改其它 class,function 。meta code 本身并不实现直接的功能
    常见的 metaprogramming 包括:装饰器 decorator,工厂函数 factory function,以及 metaclass 等,因为它们都是用于创建或修改其它 function 或 class 。
    (meta code 是我自己编的一个词语。主要目的是把它和普通的 code 区别开,表示它的作用和普通 code 不同)

  3. class metaprogramming 则是指创建的 meta code 专用于对其它 class 进行创建和修改。class decorator ,__init_subclass__ , class factory 和 metaclass 都属于 class metaprogramming 。


—————————— 本文结束 ——————————

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1922501.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【linux】服务器安装及卸载pycharm社区版教程

【linux】服务器安装及卸载pycharm社区版教程 【创作不易,求点赞关注收藏】 文章目录 【linux】服务器安装及卸载pycharm社区版教程1、到官网下载安装包2、通过终端wget下载安装包3、解压4、安装5、设置环境变量6、运行pycharm7、删除pycharm安装包、卸载pycharm …

Arcgis横向图例设置

想把这个图例改成横向的 点击图例的属性,找到样式

基于SpringBoot的校园疫情防控系统

你好,我是专注于计算机科学与技术的研究者。如果你对我的工作感兴趣或有任何问题,欢迎随时联系我。 开发语言:Java 数据库:MySQL 技术:SpringBoot框架,B/S架构 工具:Eclipse,Mav…

13 协程设计原理与汇编实现

协程的问题 为什么要有协程?协程的原语操作?协程的切换?协程的struct如何定义?协程的scheduler(调度)如何定义?调度策略如何实现?协程如何与posix,api兼容?协程多核模式?协程的性能如何测试?为什么要有协程 同步的编程方式,异步的性能。同步编程时,我们需要等待io就…

什么是STM32?嵌入式和STM32简单介绍

1、嵌入式和STM32 1.1.什么是嵌入式 除了桌面PC之外,所有的控制类设备都是嵌入式 嵌入式系统的定义:“用于控制、监视或者辅助操作机器和设备的装置”。 嵌入式系统是一个控制程序存储在ROM中的嵌入式处理器控制板,是一种专用的计算机系统。…

iPhone删除所有照片的高效三部曲

苹果手机用久了,系统缓存包括自己使用手机留下的内存肯定会越来越多。其中,相册中的照片数量可能会急剧增加,占据大量的存储空间。当用户们想要对相册进行彻底清理,实现iPhone删除所有照片时,不妨跟随以下详细的三部曲…

k8s核心操作_k8s中的存储抽象_基本概念与NFS搭建_Deployment使用NFS进行挂载---分布式云原生部署架构搭建028

然后我们继续开始看 如果我们使用容器部署,比如我们有三个节点,一个是master,一个node1 一个是node2 那么pod 中我们可以看到,容器中的 /data 等各个目录都映射了出来了,但是 如果比如上面红色的部分,有个pod,原来在node2上,最右边那个,但是这个pod宕机了 那么,k8s会在node…

【数据结构】--- 堆的应用

​ 个人主页:星纭-CSDN博客 系列文章专栏 :数据结构 踏上取经路,比抵达灵山更重要!一起努力一起进步! 一.堆排序 在前一个文章的学习中,我们使用数组的物理结构构造出了逻辑结构上的堆。那么堆到底有什么用呢&…

数据结构——考研笔记(一)绪论

目录 数据结构一、绪论1.1 数据结构的基本概念1.1.1 什么是数据?1.1.2 数据元素——描述一个个体1.1.3 什么是数据对象1.1.4 什么是数据机构 1.2 数据结构的三要素1.2.1 逻辑结构1.2.2 数据的运算1.2.3 物理结构1.2.4 数据类型、抽象数据类型1.2.5 知识回顾与重要考…

【C++】开源:paho-mqtt-cpp库配置与使用

😏★,:.☆( ̄▽ ̄)/$:.★ 😏 这篇文章主要介绍paho-mqtt-cpp库配置与使用。 无专精则不能成,无涉猎则不能通。——梁启超 欢迎来到我的博客,一起学习,共同进步。 喜欢的朋友可以关注一下&#xff…

echarts解决数据差异过大的问题

问题描述 使用echarts折线图和柱状图展示数据时,如果数据差异值较大,会导致显示图形差异过大,图表不美观。 如这一组数据[2000000, 200, 0.1, 20, 0, -10, -3000],渲染出来的效果如下图: 可以看到由于最大值和最小值差…

【经验总结】将markdown文档转换为word(swagger导出word)

工具准备: 任意markdown编辑器,以typora为例pandoc,官方下载地址 思路整理: 从swagger提取离线md文档将md文档转换为word格式 操作步骤: 一、安装pandoc (markdown编辑器安装略) 前往官网…

【学术会议征稿】第三届能源互联网及电力系统国际学术会议(ICEIPS 2024)

第三届能源互联网及电力系统国际学术会议(ICEIPS 2024) 2024 3rd International Conference on Energy Internet and Power Systems 能源互联网是实现新一代电力系统智能互动、开放共享的重要支撑技术之一,也是提升能源调度效率&#xff0…

大模型最新黑书:基于GPT-3、ChatGPT、GPT-4等Transformer架构的自然语言处理 PDF

今天给大家推荐一本丹尼斯罗斯曼(Denis Rothman)编写的关于大语言模型&#xff08;LLM&#xff09;权威教程<<大模型应用解决方案> 基于GPT-3、ChatGPT、GPT-4等Transformer架构的自然语言处理>&#xff01;Google工程总监Antonio Gulli作序&#xff0c;这含金量不…

常见的网络安全设备

一、防火墙 防火墙的核心任务&#xff1a;防护和控制&#xff0c;防火墙通过安全策略识别流量并做出相应的动作。 防火墙的安全策略在进行匹配时&#xff0c;自上而下逐一匹配&#xff0c;匹配成功则不向下进行匹配&#xff0c;末尾隐含拒绝所有规则。 1.包过滤防火墙 工作范围…

ChatGPT Mac App 发布!

2024 年 6 月&#xff0c;OpenAI 的大语言模型 ChatGPT 的 Mac 客户端与 ChatGPT-4o 一起发布了。ChatGPT Mac 户端可以让用户直接在 Mac 电脑上使用 ChatGPT 进行对话。它提供了一个简单易用的用户界面&#xff0c;用户可以在其中输入文本或语音指令&#xff0c;并接收模型生成…

(视频演示)基于OpenCV的实时视频跟踪火焰识别软件V1.0源码及exe下载

本文介绍了基于OpenCV的实时视频跟踪火焰识别软件&#xff0c;该软件通过先进的图像处理技术实现对实时视频中火焰的检测与跟踪&#xff0c;同时支持导入图片进行火焰识别。主要功能包括相机选择、实时跟踪和图片模式。软件适用于多种场合&#xff0c;用于保障人民生命财产安全…

VMWare 下给Centos扩容

目录 参考文档背景介绍扩容查看当前文件磁盘信息增加一个存储分区创建物理卷把物理卷添加到卷组查看卷组名把物理卷并入卷组 对文件系统进行扩容搞定 参考文档 1、百度经验 2、CSDN 3、掘金 背景介绍 测试环境用VMWare 安装centos7&#xff0c;几年下来磁盘空间不够用了&…

【全面介绍Photoshop,什么是Photoshop?】

🌈个人主页: 程序员不想敲代码啊 🏆CSDN优质创作者,CSDN实力新星,CSDN博客专家 👍点赞⭐评论⭐收藏 🤝希望本文对您有所裨益,如有不足之处,欢迎在评论区提出指正,让我们共同学习、交流进步! 目录 🏆前言🏆界面熟悉🏆基础工具🏆图层🏆调整与修饰🏆颜…

【postgresql】权限(Privileges)

权限&#xff08;privileges&#xff09;是决定用户或角色可以对数据库对象&#xff08;如表、视图、序列和函数&#xff09;执行哪些操作的许可。权限对于维护安全性和控制对数据的访问至关重要。 权限分类 在 PostgreSQL 中&#xff0c;权限分为以下几种&#xff1a; SELEC…