Python 實戰:別再搞混 isinstance 與 issubclass —以 python-docx 為例; 比 issubclass 更誠實的族譜 — 深入 mro()

加入好友
加入社群
Python 實戰:別再搞混 isinstance 與 issubclass —以 python-docx 為例; 比 issubclass 更誠實的族譜 — 深入 mro() - 儲蓄保險王

在操作 python-docx 進行底層 XML 修改時,我們常需要判斷一個元素到底是什麼。但你是否遇過明明繼承關係正確,程式卻回傳 False 的靈異現象?

讓我們用 CT_P(Word 文件中的段落 XML 定義)來徹底釐清這兩個函數的差異。

1. 核心觀念速記

  • isinstance(物件, 類別):問的是「你是這個模具做出來的產品嗎?
    • 檢查的是:實例 (Instance)
  • issubclass(子類別, 父類別):問的是「你的設計圖是參考那個模具畫的嗎?
    • 檢查的是:類別 (Class)

2. 實戰場景:為什麼你的程式碼回傳 False?

假設我們引入了段落的底層類別 CT_Plxml 的基礎元素 _Element

from docx.oxml.text.paragraph import CT_P
from lxml import etree

# CT_P  "類別" (Class),它是製造段落的模具
# etree._Element 也是 "類別" (Class),它是所有 XML 元素的祖先

❌ 常見錯誤:對「類別」使用 isinstance

# 你問 PythonCT_P 這個模具是由 _Element 這個模具製造出來的嗎
print(isinstance(CT_P, etree._Element)) 

# >>> False 
# 當然是 False因為 CT_P 是一個類別它是由 Python  "type" 製造的而不是由 XML 元素製造的

✅ 正確解法 A:檢查繼承關係 (使用 issubclass)

如果你想確認 CT_P 的設計圖是否繼承自 _Element

# 你問 PythonCT_P 這個模具是繼承自 _Element 家族嗎
print(issubclass(CT_P, etree._Element))

# >>> True
# 賓果這才是你想知道的
Python 實戰:別再搞混 isinstance 與 issubclass —以 python-docx 為例; 比 issubclass 更誠實的族譜 — 深入 mro() - 儲蓄保險王

✅ 正確解法 B:檢查真實物件 (使用 isinstance)

如果你手上有一個真實的段落物件

import docx

doc = docx.Document()
p = doc.add_paragraph("Hello World")

# 取得底層的 XML 物件 (這才是實例/Instance)
xml_obj = p._element 

# 你問 Python這個 xml_obj  CT_P 做出來的嗎
print(isinstance(xml_obj, CT_P))
# >>> True

# 你問 Python這個 xml_obj 算不算是 _Element 家族的產品
print(isinstance(xml_obj, etree._Element))
# >>> True
Python 實戰:別再搞混 isinstance 與 issubclass —以 python-docx 為例; 比 issubclass 更誠實的族譜 — 深入 mro() - 儲蓄保險王

總結

Python 實戰:別再搞混 isinstance 與 issubclass —以 python-docx 為例; 比 issubclass 更誠實的族譜 — 深入 mro() - 儲蓄保險王

下次看到 False 時,先停下來看一眼你的第一個參數:它是「設計圖」還是「產品」?

推薦hahow線上學習python: https://igrape.net/30afN

issubclass 更誠實的族譜 —— 深入 mro()

在上一篇我們談到用 issubclass 來確認 CT_P 是否繼承自 _Element。但 issubclass 只會告訴你「是」或「否」,卻沒告訴你中間經歷了什麼。

如果你想知道一個 python-docx 的段落元素到底混了多少血統,mro() 才是真正的照妖鏡。

1. 什麼是 MRO?

MRO 全名為 Method Resolution Order。當你呼叫一個方法(例如 paragraph.xml)時,Python 會依照這個順序去尋找該方法的定義。

  • issubclass:只告訴你「祖先裡有沒有這個人」。
  • mro():直接列出「從你自己到祖先的所有名單順序」。

2. 實戰:揭開 CT_P 的完整身世

讓我們看看 python-docx 中最常用的 CT_P(段落 XML 元素)的完整繼承鏈:

from docx.oxml.text.paragraph import CT_P

# 呼叫類別的 mro() 方法
print(CT_P.mro())

輸出結果(範例):

Python 實戰:別再搞混 isinstance 與 issubclass —以 python-docx 為例; 比 issubclass 更誠實的族譜 — 深入 mro() - 儲蓄保險王

3. mro() 告訴了我們什麼 issubclass 沒說的事?

這張列表透露了三個關鍵訊息,這對於除錯 python-docx 非常重要:

  1. 直接父類別是誰?
    你可以看到 CT_P 並不是直接繼承 lxml,中間夾了一個 BaseOxmlElement。這是 python-docx 自己封裝的一層,用來處理命名空間(namespace)和屬性映射。
    • 如果你用 issubclass,你只知道它是 _Element 的子類,但你會忽略中間這層重要的封裝。
  2. 方法被誰搶走了?
    假設 CT_PElementBase 都有一個叫 get_xml() 的方法。Python 會按照這個 List 的順序由左至右找。
    • 一旦在 BaseOxmlElement 找到了,它就會停止搜尋,不會去用 lxml 原生的。這就是為什麼有時候你呼叫 lxml 的方法會失效,因為被中間層覆寫(Override)了。
  3. 多重繼承的順序(Diamond Problem)
    雖然 CT_P 的例子比較單純,但在複雜的 Python 框架中,如果使用了多重繼承,mro() 是唯一能告訴你 Python 到底會先執行哪個父類別邏輯的工具(基於 C3 Linearization 演算法)。

4. 總結比較

Python 實戰:別再搞混 isinstance 與 issubclass —以 python-docx 為例; 比 issubclass 更誠實的族譜 — 深入 mro() - 儲蓄保險王

一句話心法

issubclass 是用來寫 if 判斷式的;mro() 是用來 Debug 找原因的。

當你發現 python-docx 的行為跟你預期的 lxml 行為不一致時,印出 .mro(),通常兇手就藏在繼承順序的第二或第三個位置裡!

推薦hahow線上學習python: https://igrape.net/30afN

加入好友
加入社群
Python 實戰:別再搞混 isinstance 與 issubclass —以 python-docx 為例; 比 issubclass 更誠實的族譜 — 深入 mro() - 儲蓄保險王

儲蓄保險王

儲蓄險是板主最喜愛的儲蓄工具,最喜愛的投資理財工具則是ETF,最喜愛的省錢工具則是信用卡

You may also like...

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *