当前位置: 首页 > news >正文

Python 类元编程(元类基础知识)

元类基础知识

元类是制造类的工厂,不过不是函数(如示例 21-2 中的
record_factory),而是类。图 21-1 使用机器和小怪兽图示法描述元
类,可以看出,元类是生产机器的机器。

元类是用于构建类的类

根据 Python 对象模型,类是对象,因此类肯定是另外某个类的实例。默
认情况下,Python 中的类是 type 类的实例。也就是说,type 是大多数
内置的类和用户定义的类的元类:

>>> 'spam'.__class__
<class 'str'>
>>> str.__class__
<class 'type'>
>>> from bulkfood_v6 import LineItem
>>> LineItem.__class__
<class 'type'>
>>> type.__class__
<class 'type'>

为了避免无限回溯,type 是其自身的实例,如最后一行所示。

注意,我没有说 str 或 LineItem 继承自 type。我的意思是,str 和
LineItem 是 type 的实例。这两个类是 object 的子类。图 21-2 可能
有助于你理清这个奇怪的现象。

两个示意图都是正确的。左边的示意图强调 str、type 和
LineItem 是 object 的子类。右边的示意图则清楚地表明
str、object 和 LineItem 是 type 的实例,因为它们都是类

object 类和 type 类之间的关系很独特:object 是 type 的
实例,而 type 是 object 的子类。这种关系很“神奇”,无法使用
Python 代码表述,因为定义其中一个之前另一个必须存在。type
是自身的实例这一点也很神奇。

除了 type,标准库中还有一些别的元类,例如 ABCMeta 和 Enum。如
下述代码片段所示,collections.Iterable 所属的类是
abc.ABCMeta。Iterable 是抽象类,而 ABCMeta 不是——不管怎
样,Iterable 是 ABCMeta 的实例:

>>> import collections
>>> collections.Iterable.__class__
<class 'abc.ABCMeta'>
>>> import abc
>>> abc.ABCMeta.__class__
<class 'type'>
>>> abc.ABCMeta.__mro__
(<class 'abc.ABCMeta'>, <class 'type'>, <class 'object'>)

向上追溯,ABCMeta 最终所属的类也是 type。所有类都直接或间接地
是 type 的实例,不过只有元类同时也是 type 的子类。若想理解元
类,一定要知道这种关系:元类(如 ABCMeta)从 type 类继承了构建
类的能力。图 21-3 对这种至关重要的关系做了图解。

图 21-3:Iterable 是 object 的子类,是 ABCMeta 的实例。object
和 ABCMeta 都是 type 的实例,但是这里的重要关系是,ABCMeta 还
是 type 的子类,因为 ABCMeta 是元类。示意图中只有 Iterable 是
抽象类

我们要抓住的重点是,所有类都是 type 的实例,但是元类还是 type
的子类,因此可以作为制造类的工厂。具体来说,元类可以通过实现
__init__ 方法定制实例。元类的 __init__ 方法可以做到类装饰器能
做的任何事情,但是作用更大,如接下来的练习所示。

理解元类计算时间的练习

我们对 21.3 节的练习做些改动,evalsupport.py 模块与示例 21-7 一样,
不过现在主脚本变成 evaltime_meta.py 了,如示例 21-10 所示。

示例 21-10 evaltime_meta.py:ClassFive 是 MetaAleph 元类的
实例

from evalsupport import deco_alpha
from evalsupport import MetaAleph
print('<[1]> evaltime_meta module start')
@deco_alpha
class ClassThree():print('<[2]> ClassThree body')def method_y(self):print('<[3]> ClassThree.method_y')
class ClassFour(ClassThree):print('<[4]> ClassFour body')def method_y(self):print('<[5]> ClassFour.method_y')
class ClassFive(metaclass=MetaAleph):print('<[6]> ClassFive body')def __init__(self):print('<[7]> ClassFive.__init__')def method_z(self):print('<[8]> ClassFive.method_z')
class ClassSix(ClassFive):print('<[9]> ClassSix body')def method_z(self):print('<[10]> ClassSix.method_z')
if __name__ == '__main__':print('<[11]> ClassThree tests', 30 * '.')three = ClassThree()three.method_y()print('<[12]> ClassFour tests', 30 * '.')four = ClassFour()four.method_y()print('<[13]> ClassFive tests', 30 * '.')five = ClassFive()five.method_z()print('<[14]> ClassSix tests', 30 * '.')six = ClassSix()six.method_z()
print('<[15]> evaltime_meta module end')

同样,请拿出纸和笔,按顺序写出下述两个场景中输出的序号标记
<[N]>。
场景 3
在 Python 控制台中以交互的方式导入 evaltime_meta.py 模块。
场景 4
在命令行中运行 evaltime_meta.py 模块。
解答和分析如下。

01. 场景3的解答
在 Python 控制台中导入 evaltime_meta.py 模块后得到的输出如示例
21-11 所示。
示例 21-11 场景 3:在 Python 控制台中导入 evaltime_meta
模块

>>> import evaltime_meta
<[100]> evalsupport module start
<[400]> MetaAleph body
<[700]> evalsupport module end
<[1]> evaltime_meta module start
<[2]> ClassThree body
<[200]> deco_alpha
<[4]> ClassFour body
<[6]> ClassFive body
<[500]> MetaAleph.__init__ ➊
<[9]> ClassSix body
<[500]> MetaAleph.__init__ ➋
<[15]> evaltime_meta module end

➊ 与场景 1 的关键区别是,创建 ClassFive 时调用了
MetaAleph.__init__ 方法。
➋ 创建 ClassFive 的子类 ClassSix 时也调用了
MetaAleph.__init__ 方法。
Python 解释器计算 ClassFive 类的定义体时没有调用 type 构建具
体的类定义体,而是调用 MetaAleph 类。看一下示例 21-12 中定
义的 MetaAleph 类,你会发现 __init__ 方法有四个参数。
self
这是要初始化的类对象(例如 ClassFive)。
name、bases、dic
与构建类时传给 type 的参数一样。
示例 21-12 evalsupport.py:定义 MetaAleph 元类,摘自示例
21-7

class MetaAleph(type):print('<[400]> MetaAleph body')def __init__(cls, name, bases, dic):print('<[500]> MetaAleph.__init__')def inner_2(self):print('<[600]> MetaAleph.__init__:inner_2')cls.method_z = inner_2

编写元类时,通常会把 self 参数改成 cls。例如,在上
述元类的 __init__ 方法中,把第一个参数命名为 cls 能清楚
地表明要构建的实例是类。

__init__ 方法的定义体中定义了 inner_2 函数,然后将其绑定给
cls.method_z。MetaAleph.__init__ 方法签名中的 cls 指代要
创建的类(例如 ClassFive)。而 inner_2 函数签名中的 self
最终是指代我们在创建的类的实例(例如 ClassFive 类的实
例)。

场景4的解答
在命令行中运行 python3 evaltime_meta.py 命令后得到的输出
如示例 21-13 所示。
示例 21-13 场景 4:在 shell 中运行 evaltime_meta.py

$ python3 evaltime.py
<[100]> evalsupport module start
<[400]> MetaAleph body
<[700]> evalsupport module end
<[1]> evaltime_meta module start
<[2]> ClassThree body
<[200]> deco_alpha
<[4]> ClassFour body
<[6]> ClassFive body
<[500]> MetaAleph.__init__
<[9]> ClassSix body
<[500]> MetaAleph.__init__
<[11]> ClassThree tests ..............................
<[300]> deco_alpha:inner_1 ➊
<[12]> ClassFour tests ..............................
<[5]> ClassFour.method_y ➋
<[13]> ClassFive tests ..............................
<[7]> ClassFive.__init__
<[600]> MetaAleph.__init__:inner_2 ➌
<[14]> ClassSix tests ..............................
<[7]> ClassFive.__init__
<[600]> MetaAleph.__init__:inner_2 ➍
<[15]> evaltime_meta module end

❶ 装饰器依附到 ClassThree 类上之后,method_y 方法被替换成
inner_1 方法……
❷ 虽然 ClassFour 是 ClassThree 的子类,但是没有依附装饰器
的 ClassFour 类却不受影响。
❸ MetaAleph 类的 __init__ 方法把 ClassFive.method_z 方法
替换成 inner_2 函数。
❹ ClassFive 的子类 ClassSix 也是一样,method_z 方法被替换
成 inner_2 函数。
注意,ClassSix 类没有直接引用 MetaAleph 类,但是却受到了影
响,因为它是 ClassFive 的子类,进而也是 MetaAleph 类的实
例,所以由 MetaAleph.__init__ 方法初始化。

如果想进一步定制类,可以在元类中实现 __new__
法。不过,通常情况下实现 __init__ 方法就够了。

现在,我们可以实践这些理论了。我们将创建一个元类,让描述符
以最佳的方式自动创建储存属性的名称。

http://www.lryc.cn/news/620142.html

相关文章:

  • 正则表达式解析(三)
  • (50)QT 绘图里,视图 QGraphicsView、场景 QGraphicsScene 及图形项 QGraphicsRectItem 的举例
  • Unity:GUI笔记(二)——工具栏和选择网格、滚动列表和分组、窗口、自定义皮肤样式、自动布局
  • 面试实战 问题二十七 java 使用1.8新特性,判断空
  • 机器学习-----DBSCAN算法
  • 电子电气架构 --- 软件项目文档管理
  • mysql的快照读与当前读的区别
  • 云电竞游戏盒子相比传统PC有什么优势?
  • YOLO-v2-tiny 20种物体检测模型
  • Unity中启用DLSS 【NVIDIA】
  • 循序渐进学 Spring (上):从 IoC/DI 核心原理到 XML 配置实战
  • AWS Bedrock Claude模型费用深度分析:企业AI成本优化指南
  • HarmonyOS Navigation路由跳转的完整示例
  • 天猫商品评论API:获取商品热门评价与最新评价
  • 销售数据预处理与分析学习总结
  • 基于UniApp的智能在线客服系统前端设计与实现
  • Github desktop介绍(GitHub官方推出的一款图形化桌面工具,旨在简化Git和GitHub的使用流程)
  • 公司项目用户密码加密方案推荐(兼顾安全、可靠与通用性)
  • Python day43
  • 【易错题】C语言
  • NTUSER.DAT是什么文件
  • Vue内置组件全解析:从入门到面试通关
  • docker安装centos
  • 接口添加了 @Transactional 注解并开启事务,而其中一个小方法启动了新线程并手动提交数据,会有什么影响?
  • 服务器安全笔记
  • 学习:JS进阶[10]内置构造函数
  • [ 数据结构 ] 泛型 (上)
  • Excel多级数据结构导入导出工具
  • Laravel 使用ssh链接远程数据库
  • Linux Framebuffer(帧缓冲)与基本 UI 绘制技术