元类基础知识

元类是制造类的工厂,不过不是函数(如示例 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.pswp.cn/news/918666.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/918666.shtml
英文地址,请注明出处:http://en.pswp.cn/news/918666.shtml

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

相关文章

【Vue 3 响应式系统深度解析:reactive vs ref 全面对比】

Vue 3 响应式系统深度解析&#xff1a;reactive vs ref 全面对比 目录 概述响应式系统基础reactive 深度分析ref 深度分析底层实现原理依赖收集机制演进解构和转换工具常见误区和陷阱技术选型指南最佳实践和建议 概述 Vue 3 引入了基于 Proxy 的全新响应式系统&#xff0c;…

JavaSE高级-01

文章目录1. 异常异常的分类自定义异常异常的处理资源关闭&#xff1a;try-with-resource2. 泛型泛型类泛型接口泛型方法、通配符、上下限通配符泛型的上下限泛型支持的类型3. 包装类4. Collection集合和Map集合4.1 Collection集合Collection集合特点Collection的遍历方式一&…

MyBatis执行器与ORM特性深度解析

一、MyBatis的Executor执行器详解1. MyBatis执行器类型MyBatis有三种核心执行器实现&#xff0c;在org.apache.ibatis.executor包中定义&#xff1a;执行器类型特点描述SimpleExecutor默认执行器&#xff0c;每次执行都会创建新的Statement对象ReuseExecutor重用预处理语句(Pre…

红黑树的特性与实现

在数据结构领域&#xff0c;二叉搜索树&#xff08;BST&#xff09;凭借 O (log n) 的平均时间复杂度成为查找、插入和删除操作的优选结构。但它有个致命缺陷&#xff1a;当输入数据有序时&#xff0c;会退化为链表&#xff0c;时间复杂度骤降至 O (n)。为解决这一问题&#xf…

ClickHouse从入门到企业级实战全解析课程简介

【课程简介】你是否正在面临这些挑战&#xff1f;海量数据的分析查询慢如蜗牛&#xff0c;报表一等就是几小时&#xff1f;想构建实时数仓&#xff0c;却不知如何高效处理 Kafka 等流式数据&#xff1f;对 ClickHouse 的众多 MergeTree 引擎感到困惑&#xff0c;不知如何选型&a…

【新启航】从人工偏差到机械精度:旋转治具让三维扫描重构数据重复精度提升至 ±0.01mm

在三维扫描重构领域&#xff0c;传统人工操作方式受限于人为因素干扰&#xff0c;数据重复精度难以保证&#xff0c;无法满足高精度工业检测与逆向工程需求。旋转治具凭借先进的机械设计与自动化控制技术&#xff0c;将三维扫描重构数据重复精度提升至 0.01mm&#xff0c;实现从…

《汇编语言:基于X86处理器》第13章 复习题和编程练习

本篇记录了《汇编语言&#xff1a;基于X86处理器》第13章 复习题和编程练习的学习笔记。13.6 复习题1.当汇编过程被高级语言程序调用时&#xff0c;主调程序与被调过程是否应使用相同的内存模式?答&#xff1a;主调程序与被调过程使用的内存模式必须相同。2.C 和 C程序调用汇编…

SpringAI智能航空助手实战<Demo>

我们将如何将我们得传统业务进行智能化的改造>>>1.将我们传统的航空票务系统 我们之前通过按钮的方式来完成 现在我们通过智能对话的方式完成 >现在我们通过对话的方式来完成 整个智能化的改造 传统应用如何进行智能化改造 我们把我们的项目通过Spring-ai 来接入A…

windows git安装步骤

1&#xff0c;从官网下载安装包&#xff1a;gitg官网 进行安装 2&#xff0c;配置git环境&#xff1a; git config --global user.name "Your Name" git config --global user.email "Your Email"3&#xff0c;生成 SSH Key&#xff1a; ssh-keygen -t r…

使用chroma和LlamaIndex做RAG增强

RAG 原理&#xff1a;通过 “检索&#xff08;从知识库获取相关信息&#xff09;→ 增强&#xff08;将信息作为上下文输入模型&#xff09;→ 生成&#xff08;模型基于上下文回答&#xff09;” 三步&#xff0c;解决大模型知识时效性、领域局限性问题。 接下来将完成这么一个…

2025 最应避免的摄影陷阱以及解决方案

你有没有想过&#xff0c;当你拍完了一个完美的场景后&#xff0c;却发现画面模糊、光线不足&#xff0c;或者更糟的是&#xff0c;存储卡中的文件丢失了&#xff1f;这些问题可能会发生在任何人身上&#xff0c;无论是业余爱好者、专业人士还是最好的摄影师。当珍贵的记忆变成…

python类--python011

面向对象编程中的类的概念、属性使用、继承和类的改造问题等。7.1 初识类在软件编程中&#xff0c;面向过程和面向对象是两种主要的编程方法。面向过程的编程强调通过函数来实现特定的功能&#xff0c;具有灵活性&#xff0c;但在复杂系统中往往导致代码重复&#xff0c;维护困…

Python函数篇:从零到精通

一、函数1.1 为什么有函数我们对于一个项目时&#xff0c;会有上千甚至上万条代码&#xff0c;当我们要使用到某个函数时&#xff0c;例如我需要计算一个求和代码&#xff0c;获得求和的值来服务我们的项目&#xff0c;那我们可能会这样#计算1&#xff5e;100的和 theSun 0 fo…

QT项目之记事本

本文用QT实现记事本功能。一、成品展示1.界面主要元素&#xff1a;1.标题为MyNoteBook&#xff1b;2.相应图标为&#xff1a;打开文件&#xff0c;保存&#xff0c;退出&#xff1b;3.右下角标注光标所在行列&#xff0c;默认编码方式为UTF-8&#xff1b;4.鼠标所在图标位置时会…

【软件测试】性能测试 —— 工具篇 JMeter 介绍与使用

&#x1f970;&#x1f970;&#x1f970;来都来了&#xff0c;不妨点个关注叭&#xff01; &#x1f449;博客主页&#xff1a;欢迎各位大佬!&#x1f448; 文章目录1. JMeter 的介绍2. JMeter 安装、配置、搭建2.1 前置条件 —— Java环境搭建2.2 JMeter 下载2.3 JMeter 安装…

二十二、Mybatis-快速入门程序

入门程序大概步骤叙述&#xff1a; 步骤一&#xff1a;创建springboot工程并且数据库提前创建表步骤二&#xff1a;创建springboot工程对Mybatis相关依赖注意打勾步骤三&#xff1a;编写查找方法步骤四&#xff1a;编写测试方法项目目录结构与数据库以及代码&#xff1a; 项目目…

Blender模拟结构光3D Scanner(一)外参数匹配

如何使用Blender模拟FPP(Fringe Projection Profilometry) 原理的结构光3D传感器&#xff1f;主要包含的工作有&#xff1a;1&#xff09;相机、投影仪定位与内外参数匹配&#xff1b;2&#xff09;投影仪投射指定Pattern图像&#xff1b;3&#xff09;被测物体材质属性配置等&…

LangChain是如何实现RAG多轮问答的

目录引言一、LangChain实现RAG多轮问答核心机制1. 对话历史管理&#xff08;Memory&#xff09;2. 问题重写&#xff08;Query Rewriting&#xff09;3. 检索增强生成&#xff08;RAG Core&#xff09;4. 链式工作流&#xff08;Chain&#xff09;二、关键设计特点三、完整示例…

DAY 44 预训练模型

知识点回顾&#xff1a; 预训练的概念常见的分类预训练模型图像预训练模型的发展史预训练的策略预训练代码实战&#xff1a;resnet18 一、预训练的概念 我们之前在训练中发现&#xff0c;准确率最开始随着epoch的增加而增加。随着循环的更新&#xff0c;参数在不断发生更新。 所…

Java Stream API 中常用方法复习及项目实战示例

在最近的练手项目中&#xff0c;对于stream流的操作愈加频繁&#xff0c;我也越来越感觉stream流在处理数据是的干净利落&#xff0c;因此写博客用来记录最近常用的方法以便于未来的复习。map() 方法map()是一个中间操作&#xff08;intermediate operation&#xff09;&#x…