Python 的 metaclass

文章目录

  • 先说结论
      • 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 inspectclass AutoSlotMeta(type): """该 metaclass 的作用是把 __init__ 中的参数,自动添加到 __slots__ 属性中。 """def __new__(meta_cls, new_cls_name, bases, namespace_dict, **kwargs):  # noqaslots = 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_classclass 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.mzph.cn/pingmian/43924.shtml

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

相关文章

Java技术栈总结:JVM虚拟机篇

一、Java的四种引用类型 1、强引用 最常见的引用,类似Object obj new Object()、String str “hello”。如果一个对象具有强引用,垃圾回收器绝对不会回收它。即使抛出“OutOfMemoryError”错误,程序终止,也不会随意回收具有强引…

20240710 每日AI必读资讯

🤖微软:不会像 OpenAI 一样阻止中国访问 AI 模型 - OpenAI 将于周二(7 月 9 日)开始阻止中国用户访问其 API。 - 微软发言人表示:Azure OpenAI API服务在中国的提供方式没有变化。 - 公司仍然通过部署在中国以外地区…

妙笔生词智能写歌词软件:创新助力还是艺术之殇?

在音乐创作日益普及和多样化的当下,各种辅助工具层出不穷,妙笔生词智能写歌词软件便是其中之一。那么,它到底表现如何呢? 妙笔生词智能写歌词软件(veve522)的突出优点在于其便捷性和高效性。对于那些灵感稍…

c/c++:牛客小白月赛93

比赛链接 A 生不逢七 题目描述(题目链接添加链接描述): 睡前游戏中最简单又最好玩的游戏就是这个啦! 该游戏规则为:多名玩家轮流报数,当要报的数字中含有 7 或者是 7 的倍数时(例如 37,49)&…

腾讯又一平台即将停止运营

随着腾讯公司业务和战略的调整,某些业务逐渐退出历史舞台,如“腾讯直播平台NOW”,以及“QQ签到”,“腾讯待办”,“企鹅FM音频平台”等,最近又有一则重磅消息,那就是“腾讯课堂”也即将停止运营。…

类似评论、省市区这种具有层次结构的数据表怎么设计?

业务功能模块 评论、回复模块省市区表 设置一个给每个数据设置一个parent_id 例如: 某个视频下a写了条评论,那a的parent_id就是0;b回复了a,那b的parent_id就是a的id;c回复了b,那c的parent_id就是b的id; 这样,所有评论…

Mosh|初学者 SQL 教程

sql文件链接:链接: https://pan.baidu.com/s/1okjsgssdxMkfKf8FEos7DA?pwdf9a9 提取码: f9a9 在mysql workbench 导入 create_databases.sql 文件,下面是运行成功的界面 快捷方式:全部运行可以同时按下controlcommandenter ,或者…

ceph存储

1 存储简介 存储的三种方式包括:块存储、文件存储、对象存储1。此外,还有内存存储、硬盘存储和闪存存储2。 内存存储:临时性数据存储方式,存储速度快,容量有限,通常用来存储正在使用的程序和数据。硬盘存…

【通信协议】八、CDL(Caterpillar Data Link)协议解析

1、协议简介 CDL(Caterpillar Data Link)是caterpillar的通信协议,该品牌发动机ECM与各控制单元进行通信时,采用基于RS-485的物理层规范进行开发的CDL协议进行通信; 2、物理层 信号传输方式:差分信号(通过两条线的电…

稀疏建模介绍,详解机器学习知识

目录 一、什么是机器学习?二、稀疏建模介绍三、Lasso回归简介四、Lasso超参数调整与模型选择 一、什么是机器学习? 机器学习是一种人工智能技术,它使计算机系统能够从数据中学习并做出预测或决策,而无需明确编程。它涉及到使用算…

集训 Day 2 模拟赛总结

复盘 7:30 开题 想到几天前被普及组难度模拟赛支配的恐惧,下意识觉得题目很难 先看 T1,好像不是很难,魔改 Kruskal 应该就行 看 T2 ,感觉很神奇,看到多串匹配想到 AC 自动机,又想了想 NOIP …

328. 奇偶链表

https://leetcode.cn/problems/odd-even-linked-list/https://leetcode.cn/problems/odd-even-linked-list/ 解题思路: 把第一个和第二个节点分别作为奇数、偶数的头节点,当遇到奇节点,删除,并插入到奇数头节点后,这样…

PPI(每英寸像素数)、DPI(每英寸点数)和Pixel(像素)的区别和联系?

一、定义 PPI、DPI和Pixel是图像处理、打印和显示领域中常用的三个概念,它们之间既有区别又有联系。以下是对这三个概念进行分别讲解: 1. PPI(Pixels Per Inch)-即每英寸像素数,是图像分辨率的一种表示方…

理解点对点协议:构建高效网络通信

在通信线路质量较差的年代,能够实现可靠传输的高级数据链路控制(High-level Data Link Control, HDLC)协议曾是比较流行的数据链路层协议。HDLC是一个较复杂的协议,实现了滑动窗口协议,并支持点对点和点对多点两种连接…

钉钉扫码登录第三方

钉钉文档 实现登录第三方网站 - 钉钉开放平台 (dingtalk.com) html页面 将html放在 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><title>登录</title>// jquery<script src"http://code.jqu…

Qt:12.输入类控件(QSpinBox-整数值输入的小部件、QDateEdit、QTimeEdit、QDateTimeEdit- 日期和时间输入的控件)

目录 一、QSpinBox-整数值输入的小部件&#xff1a; 1.1QSpinBox介绍&#xff1a; 1.2属性介绍&#xff1a; 1.3通用属性介绍&#xff1a; 1.4信号介绍&#xff1a; 二、QDateEdit、QTimeEdit、QDateTimeEdit- 日期和时间输入的控件&#xff1a; 2.1QDateEdit、QTimeEdit…

机器人伦理分析:从扫地机器人到智能伙伴

我发过一个泡泡&#xff1a;机器人和扫地机器人。 意犹未尽&#xff0c;我觉得这是一个值得讨论下去的话题。或者是未来话题 在科技迅猛发展的今天&#xff0c;机器人已经从简单的执行工具演变为能够执行复杂任务的智能实体。特别是在家庭环境中&#xff0c;扫地机器人已经成为…

Mac下flutter运行iOS模拟器

上篇flutter环境安装&#xff08;Macvscode&#xff09;已经将vscode和xcode等开发环境都搭建起来了&#xff0c;vscode新建工程还是比较方便的&#xff0c;那么&#xff0c;建立好了之后&#xff0c;我们怎么看效果呢&#xff1f; 1. vscode新建项目 通过 vscode的命令命板(…

零信任网络安全

随着数字化转型的发生&#xff0c;网络边界也在不断被重新定义&#xff0c;因此&#xff0c;组织必须使用新的安全方法重新定义其防御策略。 零信任是一种基于“永不信任&#xff0c;永远验证”原则的安全方法&#xff0c;它强调无论在公司内部或外部&#xff0c;任何用户、设…

如何检测用户的Chrome插件

背景 已知一些Chrome插件会影响到网站的一些功能&#xff0c;希望在前端主动检测到用户使用了某插件然后弹出提示让用户关闭&#xff0c;以减少客诉 方法 1. 检测资源文件 如图获取插件的ID 启用插件后&#xff0c;打开 chrome-extension://${ID}/manifest.json 找到 web_a…