在操作 python-docx 進行底層 XML 修改時,我們常需要判斷一個元素到底是什麼。但你是否遇過明明繼承關係正確,程式卻回傳 False 的靈異現象?
讓我們用 CT_P(Word 文件中的段落 XML 定義)來徹底釐清這兩個函數的差異。
1. 核心觀念速記
isinstance(物件, 類別):問的是「你是這個模具做出來的產品嗎?」- 檢查的是:實例 (Instance)。
issubclass(子類別, 父類別):問的是「你的設計圖是參考那個模具畫的嗎?」- 檢查的是:類別 (Class)。
2. 實戰場景:為什麼你的程式碼回傳 False?
假設我們引入了段落的底層類別 CT_P 和 lxml 的基礎元素 _Element:
from docx.oxml.text.paragraph import CT_P
from lxml import etree
# CT_P 是 "類別" (Class),它是製造段落的模具
# etree._Element 也是 "類別" (Class),它是所有 XML 元素的祖先❌ 常見錯誤:對「類別」使用 isinstance
# 你問 Python:CT_P 這個「模具」是由 _Element 這個「模具」製造出來的嗎?
print(isinstance(CT_P, etree._Element))
# >>> False
# 當然是 False!因為 CT_P 是一個類別,它是由 Python 的 "type" 製造的,而不是由 XML 元素製造的。✅ 正確解法 A:檢查繼承關係 (使用 issubclass)
如果你想確認 CT_P 的設計圖是否繼承自 _Element:
# 你問 Python:CT_P 這個模具,是繼承自 _Element 家族嗎?
print(issubclass(CT_P, etree._Element))
# >>> True
# 賓果!這才是你想知道的。
✅ 正確解法 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
總結

下次看到 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())輸出結果(範例):

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

一句話心法
issubclass是用來寫if判斷式的;mro()是用來 Debug 找原因的。
當你發現 python-docx 的行為跟你預期的 lxml 行為不一致時,印出 .mro(),通常兇手就藏在繼承順序的第二或第三個位置裡!
推薦hahow線上學習python: https://igrape.net/30afN
![Python如何串接OpenAI /Claude /Gemini API自動將大量維修紀錄JSON轉自然語言描述(並避免中斷資料遺失)response = client.chat.completions.create() ; reply = response.choices[0].message.content Python如何串接OpenAI /Claude /Gemini API自動將大量維修紀錄JSON轉自然語言描述(並避免中斷資料遺失)response = client.chat.completions.create() ; reply = response.choices[0].message.content](https://i2.wp.com/savingking.com.tw/wp-content/uploads/2025/07/20250716084059_0_c5b368.png?quality=90&zoom=2&ssl=1&resize=350%2C233)









近期留言