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.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…