2024年4月30日 星期二

露天購買 OPPO A38 保護套+螢幕保護貼 x 2

因為爸的小米 POKO 手機電池膨脹 (品質顛覆我之前對小米的肯定), 今天在 momo 買 OPPO A38 新機, 同時也上露天買了保護套與螢幕保護貼各 2 個 :





超取免運, 合計 236+198=434 元. 


2024-05-01 補充 :

今天到貨, 18TB 硬碟實際上只有 16.2TB (之前就有購買者留言) :




2.5 吋 5TB 其實也只有 4.5TB. 

霸的 OPPO 手機也設定好了, 質感還不錯. 


momo 購買 OPPO A38 手機/WD 外接硬碟 (5T, 18T)/功率計

最近看到 momo 外接硬碟有優惠, 買了 WD 2.5 吋 5TB 與 3.5 吋 18TB 各一顆, 另外很久之前想買的功率計, 還有爸的小米 POCO 手機無法開機 (電池膨脹), 改買 OPPO A38, 今天一起買了 : 
 

總計 18403 元, 扣掉 73 元 MOMO 幣, 實付 18329 元 :




本來想買 Seagate 這款 5TB, 保固多一年 (3 年) :


但在 CP 值考慮下還是都買 WD 的. 

OPPO 有 A38/A57 兩款, A57 ROM 才 64GB, 價格又差不多, 當然選 A38 :


2024年4月29日 星期一

誠品買書兩本 : 無線通訊射頻晶片模組設計 (系統篇 + 晶片篇)

最近收到誠品通知, 去年在誠品線上搜尋的兩本書已到貨上架 (那時有按到貨通知鈕), 我問二哥有沒有需要買, 他說這兩本是做類比射頻 IC 的聖經, 目前做研究都用得上, 所以今天上網買了, 剛好有母親節活動 85 折 :





說實在的, 二哥這領域我看不懂了, 只能幫他買書. 

2024年4月28日 星期日

2024 年第 17 周記事

本周專注於學習如何用 Python 套件剖析 XML 文件, 雖然現在主流資料交換格式是 JSON, 但仍有不少網站提供 XML 文件下載, 所以在寫爬蟲程式時會用到. 我十幾年前學網頁設計時就買過好幾本 XML 的書, 但那時覺得 XML 實在太難搞, 沒有一本讀完的. 如今用 Python 來剖析它卻覺得不難啊! 這就如同小時候的三合院, 印象中空間很大, 走出去到村外小圳也有點距離, 但長大後回去看卻都縮小了尺寸, 我認為這是因為我長高了, 視角不同了比例尺也不同.  

上週日在馬路旁給芒果套袋時可能因為穿汗衫沒做防護, 被不明昆蟲炎上, 結束後覺得癢癢的, 第二天手上與前胸都冒出奇癢的疹子, 明顯過敏現象, 三天後才緩解下來. 今天我就穿長袖戴帽子還噴香茅驅蚊也驅蟲. 

中午爸從阿泉伯那邊回來時, 半路遇到狗打架閃避時自摔, 左腳踝被機車壓到走路會痛, 下午載他去署立醫院急診照 X 光, 還好沒有骨折, 醫生研判是肌肉拉傷, 打了針拿消炎與止痛藥回家, 每天冰敷痠痛處 4 次, 每次 10 分鐘. 這件意外讓我打算想買一台三輪電動車或機車裝輔助輪, 三輪比較穩.  之前有想要買一台二手車給爸雨天代步, 但小舅說 80 歲以上老人家不適合開車就作罷了. 看來三輪機車是可考慮的選項. 

市圖還書 1 本 : Python不廢話 一行程式碼

週五去河堤還下面這本書 (有人預約) : 


Source : 博客來


此書主旨是以精簡方式 (目標為一行指令) 完成程式目標, 寫出 pythonic 的程式碼, 例如串列生成式就是典型手法. 作者的網站提供豐富的 Python 教學內容與可供下載的 cheatsheet, 參考 : 


穿牆王 WiFi 中繼器設定與測試

我去年 11/19 在蝦皮買了兩個穿牆王 WiFi 中繼器, 參考 : 


一直想要開箱測試卻沒時間, 今晚趁著看淚之女王前的空檔來設定, 首先將穿牆王插在插座上送電, 底下 2 個燈會先亮, 最底下是電源, 其上為 WiFi 信號燈 : 




說明書說最快的中繼設定方式是透過 WPS 功能進行綁定, 先按路由器的 WPS 鍵, 再按穿牆王的 WPS 鍵 (左側) 即可. 但我正在下載資料, 不確定 WPS 會不會影響下載, 所以還是用瀏覽器去設定. 先開啟手機或 PC 的 WiFi 連線, 這時會搜尋到附近的所有熱點, 找尋 SSID 是 Repeater-xxxx 的那個 (xxxx 是穿牆王 MAC 最後四碼), 點選它連線穿牆王的設定用網頁伺服器 :




這時因為穿牆王還沒有 Internet 連線, 所以會顯示 "網際網路可能無法使用", 請點選 "一律連接" : 




說明書說會自動開啟瀏覽器顯示設定頁面, 但我手機不會, 所以依照說明書建議自己開啟瀏覽器, 輸入 網址 196.168.11.1 :



按 LOGIN 會擬選擇穿牆王工作模式, 選 "Repeater Mode Settings" 那項的 NEXT 鈕 :



進下一頁選擇要被中繼的路由器 SSID :




輸入其連線密碼後按 NEXT 就完成設定了 : 




如果沒有勾選密碼底下的選項, 則中繼器的 SSD 會預設為路由器 SSID 後面加上 '-2.4G-ext', 例如我的華碩路由器是 ASUS-RT-AX3000, 則中繼器預設就是 ASUS-RT-AX3000-2.4G-ext :




等待 30 秒後第三個燈 (兩個波) 就會亮起來 :




這時手機重新掃描 WiFi 就會看到中繼器的 SSID 了 :




連線中繼器後用台大測速網站測試, 下載 50M, 上傳 39M :




完工啦. 


2024-04-29 補充 :

今天設定第二支穿牆王, 我以為可以輸入第一支穿牆王的 SSID, 這樣就可以接力下去把信號延伸到很遠的地方, 但想得太美了, 這不是 MESH 型態的網路, 每一支穿牆王都只能連線路由器, 不能連線另一支中繼器, 也就是說它只能組成以路由器為中心的 STAR 網路. 其次, 每一支中繼器若都不自訂 SSID, 預設都會是以 '-2.4G-ext' 結尾, 所以每一支的 SSID 都一樣, 這樣對物聯網程式較方便, 因為都是連線攜同名稱的中繼器 SSID 不用改程式. 

2024年4月27日 星期六

QRV 更換發電機與電瓶

上個月 Nissan QRV 做 13 萬公里保養時技師建議換電池與發電機, 因時間不夠所以預約今天去大中服務廠更換. 電池已超過兩年, 檢修數據還算 OK 可以續用, 我最長曾撐到 3 年半才換, 但超過兩年隨時都有可能掛點, 如果是假日或在鄉下就麻煩了, 保險起見還是換了. 

發電機是前兩次檢修時發現軸承有問題, 雖不影響供電但可能損傷皮帶, 建議整顆換新連皮帶也一起換. 工錢 1000 元合計原價是 12700 左右, 聯名卡優惠 7 折實付 9706 元 :




上個月保養後仍出現 2~3 次等紅綠燈怠速時熄火現象, 黃師傅掛電腦檢修均正常, 如果要細檢須留廠一項項排除頗費時間, 他說發電機也是其中一項, 或許這次換新也可能解決此問題, 況且這一個月來也沒再發生, 所以就再觀察看看. 

更換完成後接到水某電話, 問我檢修結束沒有, 原來她早上要去義大幫病人抽血, 但我要保修車子只好去博愛路坐高雄客運, 出乎意料的是司機到站居然沒停, 下一班要 40 分鐘後, 我說快好了走路過來大中路, 交好車經仁武到義大, 發電機換新後起步時常聽到細微的高頻音也消失了, 每次上高速公路加速時水某常問怎會有這種聲音原來是發電機加速旋轉產生的. 

峰大師自從買了 XTrail e-power 就一直勸我換車, 但我覺得買車要花掉 150 萬不符我的理財規範, 那可以買 40 張 0056 耶! 我還是繼續開熟悉又好用的 QRV. 

料理實驗 : 用 Bruno 壓力鍋做雞肉蔬菜燉飯

周四晚上菁菁回家, 問她是否還要吃馬鈴薯燉牛肉, 她說有點膩了, 我就找了 Bruno 的食譜, 其中燉飯以前沒做過, 剛好冰箱有雞胸肉, 鴻喜菇, 與高麗菜, 番茄等, 那就來做雞肉蔬菜燉飯吧!


我還參考了下面幾個食譜, 發現大家作法與食材都有些不同, 其實不用太拘泥, 不論是用壓力鍋還是一般電鍋, 只要掌握一個重要比例即可 : 水與米的比例是一比一 :













材料 : 
  • 米 2 杯
  • 雞胸肉 1 片
  • 洋蔥 1 顆
  • 高麗菜 適量
  • 鴻喜菇 1 朵
  • 番茄 1 顆 (選用)
  • 醬油 適量
  • 味晽 適量
  • 米酒 適量
作法 :
  1. 高麗菜洋蔥切絲, 鴻喜菇洗淨備用. 
  2. 米洗淨放入壓力鍋, 雞胸肉切片或切塊放上面, 上面鋪高麗菜絲, 鴻喜菇, 與洋蔥絲等, 加適量醬油 (約 0.5~1 量米杯), 味晽與米酒, 加與米杯數相同水 (1:1, 兩杯米就兩杯水), 放入壓力鍋, 按手動鈕, 設定加熱 20 分鐘 (開機按 "手動" 鈕後, 連續按 + 直到 P20 即可). 
洩壓後開啟鍋蓋, 用飯匙翻攪菜與飯後再蓋上鍋蓋燜 5 分鐘. 




米飯軟有醬油香, 非常好吃, 第一次做燉飯大成功!

2024年4月24日 星期三

Python 學習筆記 : 用 xmltodict 模組讀寫 .xml 檔

我在 "超圖解 Python 程式設計入門" 這本書中找到一個 Python 第三方的套件 xmltodict, 可讀取 XML 文件並轉成 Python 字典, 在擷取 XML 文件資訊上非常方便, 比起 Python 內建 xml 套件的 ElementTree 模組要簡單多了. 關於 ElementTree 模組用法參考 :


首先用 pip install 安裝套件 :

D:\Python\test>pip install xmltodict   
Collecting xmltodict
  Downloading xmltodict-0.13.0-py2.py3-none-any.whl (10.0 kB)
Installing collected packages: xmltodict
Successfully installed xmltodict-0.13.0 

>>> import xmltodict   
>>> xmltodict.__version__   
'0.13.0'

用 dir() 檢視套件內容 : 

>>> dir(xmltodict)   
['AttributesImpl', 'ParsingInterrupted', 'StringIO', 'XMLGenerator', '_DictSAXHandler', '__author__', '__builtins__', '__cached__', '__doc__', '__file__', '__license__', '__loader__', '__name__', '__package__', '__spec__', '__version__', '_basestring', '_dict', '_emit', '_process_namespace', '_unicode', 'expat', 'isgenerator', 'parse', 'platform', 'unparse']

使用下列自訂模組 members.py 來檢視其內容 :

# members.py
import inspect 
def varname(x): 
    return [k for k,v in inspect.currentframe().f_back.f_locals.items() if v is x][0]
def list_members(parent_obj):
    members=dir(parent_obj)
    parent_obj_name=varname(parent_obj)       
    for mbr in members:
        child_obj=eval(parent_obj_name + '.' + mbr) 
        if not mbr.startswith('_'):
            print(mbr, type(child_obj))  

將此函式存成 members.py 模組, 放在目前供作目錄下, 然後匯入其 list_members() 函式來檢視 xmltodict 模組 : 

>>> from members import list_members   
>>> list_members(xmltodict)   
AttributesImpl <class 'type'>
ParsingInterrupted <class 'type'>
StringIO <class 'type'>
XMLGenerator <class 'type'>
expat <class 'module'>
isgenerator <class 'function'>
parse <class 'function'>
platform <class 'module'>
unparse <class 'function'>

我們會用到的函式只有兩個 : 
  • parse(file) : 讀取 .xml 檔轉成字典傳回
  • unparse(dict) :  將字典轉成 XML 字串
以下仍以之前測試 xml 套件的顯示卡 XML 文件 display_card.xml 為例 :

<?xml version="1.0" encoding="UTF-8"?>
<顯示卡>
  <型號>NVidia RTX3060
    <GPU核心編號>GA106-300</GPU核心編號>
  <CUDA核心數>3584</CUDA核心數>
  <TensorCores>112</TensorCores>
  <VRAM 單位="GB" DDR="DDR6">12G</VRAM>
  <ResizableBAR支援>有</ResizableBAR支援>
  </型號>
</顯示卡> 

用 with open() 以 utf-8 編碼開啟 .xml 檔, 讀取檔案內容後傳給 xmltodict.parse(), 它會將 XML 文件剖析為一個字典傳回 : 

>>> with open('display_card.xml', encoding='utf-8') as f:  
    data=xmltodict.parse(f.read())  
    
>>> data   
{'顯示卡': {'型號': {'GPU核心編號': 'GA106-300', 'CUDA核心數': '3584', 'TensorCores': '112', 'VRAM': {'@單位': 'GB', '@DDR': 'DDR6', '#text': '12G'}, 'ResizableBAR支援': '有', '#text': 'NVidia GTX3060'}}}
>>> type(data)    
<class 'dict'>

這樣就用字典的存取方式很容易取得 XML 裡面的資料了, 例如 : 

>>> data['顯示卡']   
{'型號': {'GPU核心編號': 'GA106-300', 'CUDA核心數': '3584', 'TensorCores': '112', 'VRAM': {'@單位': 'GB', '@DDR': 'DDR6', '#text': '12G'}, 'ResizableBAR支援': '有', '#text': 'NVidia GTX3060'}}
>>> data['顯示卡']['型號']   
{'GPU核心編號': 'GA106-300', 'CUDA核心數': '3584', 'TensorCores': '112', 'VRAM': {'@單位': 'GB', '@DDR': 'DDR6', '#text': '12G'}, 'ResizableBAR支援': '有', '#text': 'NVidia GTX3060'}
>>> data['顯示卡']['型號']['GPU核心編號']   
'GA106-300'

可見無屬性的標籤會以其內容當作值; 有屬性的標籤, 其值為一個字典, 屬性的鍵會以 @ 開頭後面跟著屬性名稱, 而內容的鍵則固定為 #text. 

也可以呼叫字典物件的方法 :

>>> data['顯示卡']['型號'].keys()    # 傳回鍵串列
dict_keys(['GPU核心編號', 'CUDA核心數', 'TensorCores', 'VRAM', 'ResizableBAR支援', '#text'])
>>> data['顯示卡']['型號'].values()   # 傳回值串列
dict_values(['GA106-300', '3584', '112', {'@單位': 'GB', '@DDR': 'DDR6', '#text': '12G'}, '有', 'NVidia GTX3060'])
>>> data['顯示卡']['型號'].items()     # 傳回鍵值對串列
dict_items([('GPU核心編號', 'GA106-300'), ('CUDA核心數', '3584'), ('TensorCores', '112'), ('VRAM', {'@單位': 'GB', '@DDR': 'DDR6', '#text': '12G'}), ('ResizableBAR支援', '有'), ('#text', 'NVidia GTX3060')])

新增標籤可以呼叫字典的 update({key, value}) 方法或 setdefault(key, value) :

>>> data['顯示卡']['型號'].update({"重量": "996g"})   
>>> data['顯示卡']['型號']    
{'GPU核心編號': 'GA106-300', 'CUDA核心數': '3584', 'TensorCores': '112', 'VRAM': {'@單位': 'GB', '@DDR': 'DDR6', '#text': '12G'}, 'ResizableBAR支援': '有', '#text': 'NVidia GTX3060', '重量': '996g'}

如果是有屬性的標籤, 則字典的值也是一個字典, 屬性在名稱前冠 @ 為鍵; 內容則以 #text 為鍵 :

>>> data['顯示卡']['型號'].update({"功耗": {"@單位": "瓦", "#text": "170W"}})    
>>> data['顯示卡']['型號']    
{'GPU核心編號': 'GA106-300', 'CUDA核心數': '3584', 'TensorCores': '112', 'VRAM': {'@單位': 'GB', '@DDR': 'DDR6', '#text': '12G'}, 'ResizableBAR支援': '有', '#text': 'NVidia GTX3060', '重量': '996g', '功耗': {'@單位': '瓦', '#text': '170W'}}

下面使用 set_default(key, value) 來新增節點 :

>>> data['顯示卡']['型號'].setdefault('HDCP支援', "有")    
'有'
>>> data['顯示卡']['型號']   
{'GPU核心編號': 'GA106-300', 'CUDA核心數': '3584', 'TensorCores': '112', 'VRAM': {'@單位': 'GB', '@DDR': 'DDR6', '#text': '12G'}, 'ResizableBAR支援': '有', '#text': 'NVidia GTX3060', '重量': '996g', '功耗': {'@單位': '瓦', '#text': '170W'}, 'HDCP支援': '有'}

如果標籤有屬性, 則 value 就傳入一個字典, 屬性在名稱前冠 @ 為鍵; 標籤的內容則固定以 #text 為鍵 :

>>> data['顯示卡']['型號'].setdefault('核心時脈', {'@單位': 'MHz', '#text': '1777'})   
{'@單位': 'MHz', '#text': '1777'}
>>> data['顯示卡']['型號']   
{'GPU核心編號': 'GA106-300', 'CUDA核心數': '3584', 'TensorCores': '112', 'VRAM': {'@單位': 'GB', '@DDR': 'DDR6', '#text': '12G'}, 'ResizableBAR支援': '有', '#text': 'NVidia GTX3060', '重量': '996g', '功耗': {'@單位': '瓦', '#text': '170W'}, 'HDCP支援': '有', '核心時脈': {'@單位': 'MHz', '#text': '1777'}}

呼叫 unparse() 函式並傳入字典可將其轉回 XML 文件, 語法如下 :

xmltodict.unparse(dict, [encoding='utf-8', full_document=True, pretty=False])  

參數說明如下 :
  • encoding : 預設為 utf-8 編碼
  • full_document : 輸出完整 XML 文件, 預設為 True
  • pretty : 是否將輸出文件, 預設 False
檢視目前修改後的 data 字典 :

>>> data   
{'顯示卡': {'型號': {'GPU核心編號': 'GA106-300', 'CUDA核心數': '3584', 'TensorCores': '112', 'VRAM': {'@單位': 'GB', '@DDR': 'DDR6', '#text': '12G'}, 'ResizableBAR支援': '有', '#text': 'NVidia GTX3060', '重量': '996g', '功耗': {'@單位': '瓦', '#text': '170W'}, 'HDCP支援': '有', '核心時脈': {'@單位': 'MHz', '#text': '1777'}}}}

為了讓輸出格式化, 匯入 pprint.pprint() 函式 :

>>> from pprint import pprint    
>>> xml_str=xmltodict.unparse(data)   
>>> type(xml_str)   
<class 'str'>  
>>> pprint(xml_str)   
('<?xml version="1.0" encoding="utf-8"?>\n'
 '<顯示卡><型號><GPU核心編號>GA106-300</GPU核心編號><CUDA核心數>3584</CUDA核心數><TensorCores>112</TensorCores><VRAM '
 '單位="GB" '
 'DDR="DDR6">12G</VRAM><ResizableBAR支援>有</ResizableBAR支援><重量>996g</重量><功耗 '
 '單位="瓦">170W</功耗><HDCP支援>有</HDCP支援><核心時脈 單位="MHz">1777</核心時脈>NVidia '
 'GTX3060</型號></顯示卡>')

傳入 pretty=True 會輸出格式化字串 : 

>>> xml_str=xmltodict.unparse(data, pretty=True)    
>>> pprint(xml_str)     
('<?xml version="1.0" encoding="utf-8"?>\n'
 '<顯示卡>\n'
 '\t<型號>\n'
 '\t\t<GPU核心編號>GA106-300</GPU核心編號>\n'
 '\t\t<CUDA核心數>3584</CUDA核心數>\n'
 '\t\t<TensorCores>112</TensorCores>\n'
 '\t\t<VRAM 單位="GB" DDR="DDR6">12G</VRAM>\n'
 '\t\t<ResizableBAR支援>有</ResizableBAR支援>\n'
 '\t\t<重量>996g</重量>\n'
 '\t\t<功耗 單位="瓦">170W</功耗>\n'
 '\t\t<HDCP支援>有</HDCP支援>\n'
 '\t\t<核心時脈 單位="MHz">1777</核心時脈>\n'
 'NVidia GTX3060\t</型號>\n'
 '</顯示卡>')

用 with open() 將轉換結果寫入檔案 :

>>> with open('display_card_5.xml', 'w', encoding='utf-8') as f:   
    f.write(xml_str)   
    
332

開啟檔案 display_card_5.xml 內容如下 : 

<?xml version="1.0" encoding="utf-8"?>
<顯示卡>
<型號>
<GPU核心編號>GA106-300</GPU核心編號>
<CUDA核心數>3584</CUDA核心數>
<TensorCores>112</TensorCores>
<VRAM 單位="GB" DDR="DDR6">12G</VRAM>
<ResizableBAR支援>有</ResizableBAR支援>
<重量>996g</重量>
<功耗 單位="瓦">170W</功耗>
<HDCP支援>有</HDCP支援>
<核心時脈 單位="MHz">1777</核心時脈>
NVidia GTX3060 </型號>
</顯示卡>

這樣就把字典轉換成 XML 文件了, 比用 xml 的 ElementTree 模組要簡單許多. 

高科大還書 1 本

因為預約書到館, 所以拿下面這本去換 :


此書我幾乎看完了, 只剩最後兩章迴歸與分類的尚未做筆記, 有空再借回來補寫. 

2024年4月22日 星期一

Python 學習筆記 : 用 ElementTree 套件讀寫 .xml 檔

可延伸標記語言 XML (eXtensible Markup Language) 是簡化自 SGML (Standard Generalized Markup Language) 的一個標記語言, 是一種機器與人類皆可讀, 以 unicode 編碼的純文字資料交換格式. XML 與 HTML 最大的差異是 XML 的標籤可自訂且可從名稱理解資訊內容, 而 HTML 的標籤則是 W3C 所制定, 無法從標籤名稱理解資訊內容. 

XML 語法摘要如下 :
  • XML 是用來標記資料的結構的格式.
  • XML 檔案以 .xml 為副檔名, 第一行須聲明版本與編碼格式 :

    <?xml version="1.0" encoding="UTF-8"?>   

  • XML 檔支援任何 Unicode 編碼,  utf-8 格式為大部分剖析器接受.
  • XML 標籤名稱有分大小寫 (HTML 不分) 且不可有空格.
  • 標籤可以多層嵌套. 起始標籤內可以有屬性. 
  • 資料存放的位置可在屬性與其值,  或者是子標籤
例如下面的顯示卡 XML 文件 display_card.xml :

<?xml version="1.0" encoding="UTF-8"?>
<顯示卡>
  <型號>NVidia RTX3060
    <GPU核心編號>GA106-300</GPU核心編號>
  <CUDA核心數>3584</CUDA核心數>
  <TensorCores>112</TensorCores>
  <VRAM 單位="GB" DDR="DDR6">12G</VRAM>
  <ResizableBAR支援>有</ResizableBAR支援>
  </型號>
</顯示卡> 

將此 XML 碼以 utf-8 編碼格式存在工作目錄下的 display_card.xml, 然後用 Python 內建套件 xml 來讀取並剖析. xml 套件由多層子模組構成, 我們用來剖析 XML 文件的其實是 xml.etree 下的 ElementTree 模組, 教學文件參考 :


參考書籍 :



一. 剖析與走訪 XML 文件的語法樹 : 

首先來檢視 xml 套件之結構 : 

>>> import xml   
>>> dir(xml)   
['__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', 'etree']  
>>> type(xml.etree)  
<class 'module'> 

可見 xml 套件底下只有一個模組 etree, 用 dir() 檢視其內容 :

>>> dir(xml.etree)  
['ElementPath', 'ElementTree', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__']

可見 xml.etree 底下又有兩個模組 :

>>> type(xml.etree.ElementTree)    
<class 'module'>
>>> type(xml.etree.ElementPath)   
<class 'module'>  

此處會用到的是 ElementTree 這個子模組, 所以通常會直接匯入此模組並取簡名為 ET :

>>> import xml.etree.ElementTree as ET   

用 dir() 檢視其內容 :

>>> dir(ET)   
['C14NWriterTarget', 'Comment', 'Element', 'ElementPath', 'ElementTree', 'HTML_EMPTY', 'PI', 'ParseError', 'ProcessingInstruction', 'QName', 'SubElement', 'TreeBuilder', 'VERSION', 'XML', 'XMLID', 'XMLParser', 'XMLPullParser', '_Element_Py', '_ListDataStream', '__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', '_escape_attrib', '_escape_attrib_c14n', '_escape_attrib_html', '_escape_cdata', '_escape_cdata_c14n', '_get_writer', '_looks_like_prefix_name', '_namespace_map', '_namespaces', '_raise_serialization_error', '_serialize', '_serialize_html', '_serialize_text', '_serialize_xml', '_set_factories', 'canonicalize', 'collections', 'contextlib', 'dump', 'fromstring', 'fromstringlist', 'indent', 'io', 'iselement', 'iterparse', 'parse', 're', 'register_namespace', 'sys', 'tostring', 'tostringlist', 'warnings']

使用下列自訂模組 members.py 來檢視其內容會更清楚每個成員是函式或類別 :

# members.py
import inspect 
def varname(x): 
    return [k for k,v in inspect.currentframe().f_back.f_locals.items() if v is x][0]
def list_members(parent_obj):
    members=dir(parent_obj)
    parent_obj_name=varname(parent_obj)       
    for mbr in members:
        child_obj=eval(parent_obj_name + '.' + mbr) 
        if not mbr.startswith('_'):
            print(mbr, type(child_obj))  

將此函式存成 members.py 模組, 放在目前工作目錄下, 然後匯入 list_members() 函式來檢視 ElementTree 模組 : 

>>> from members import list_members 
>>> list_members(ET)   
>>> list_members(xml.etree.ElementTree)
C14NWriterTarget <class 'type'>
Comment <class 'function'>
Element <class 'type'>
ElementPath <class 'module'>
ElementTree <class 'type'>
HTML_EMPTY <class 'set'>
PI <class 'function'>
ParseError <class 'type'>
ProcessingInstruction <class 'function'>
QName <class 'type'>
SubElement <class 'builtin_function_or_method'>
TreeBuilder <class 'type'>
VERSION <class 'str'>
XML <class 'function'>
XMLID <class 'function'>
XMLParser <class 'type'>
XMLPullParser <class 'type'>
canonicalize <class 'function'>
collections <class 'module'>
contextlib <class 'module'>
dump <class 'function'>
fromstring <class 'function'>
fromstringlist <class 'function'>
indent <class 'function'>
io <class 'module'>
iselement <class 'function'>
iterparse <class 'function'>
parse <class 'function'>
re <class 'module'>
register_namespace <class 'function'>
sys <class 'module'>
tostring <class 'function'>
tostringlist <class 'function'>
warnings <class 'module'>

其中兩個成員可以用來建立子節點 :
  • Element 類別
  • SubElement 類別
有三個成員可用來建立 XML 語法樹以便進行資料剖析 : 
  • ElementTree 類別 : 呼叫其建構式 ElementTree() 會傳回 ElementTree 物件
  • parse() 函式 : 傳回 ElementTree 物件 (整棵 XML 語法樹)
  • fromstring() 函式 : 傳回 XML 語法樹的根節點 (Element 物件)
除了 fromstring() 函式是傳入 XML 字串外, parse() 與 ElementTree() 都是傳入 .xml 檔的路徑與檔名. 注意, fromstring() 傳回的不是代表整棵 XML 語法樹的 ElementTree 物件 , 而是它底下的根節點 Element 物件.  


1. 使用 parse() 函式剖析 XML 文件 :  

parse() 函式用來載入外部 .xml 檔案並剖析為 XML 語法樹, 傳入值為 .xml 文件的路徑與檔名, 傳回值為一個 ElementTree 物件, 語法如下 :

tree=ET.parse(xml_file)    

當呼叫 parse() 函式載入 XML 文件後會將其剖析為一棵由 ElementTree 物件與若干 Element 物件組成的 XML 語法樹, 以上面的顯示卡 XML 文件 display_card.xml 為例 :
 
>>> import xml.etree.ElementTree as ET   # 匯入 ElementTree 模組
>>> tree=ET.parse('display_card.xml')   
>>> type(tree)    
<class 'xml.etree.ElementTree.ElementTree'>   

這個 ElementTree 物件其實就是 XML 語法樹, 代表了整份 XML 文件. 用 dir() 檢視此 ElementTree 物件之內容 : 

>>> dir(tree)   
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_root', '_setroot', 'find', 'findall', 'findtext', 'getroot', 'iter', 'iterfind', 'parse', 'write', 'write_c14n']

用 list_members() 可以得知哪些是函式哪些是類別 :

>>> list_members(tree)   
find <class 'method'>
findall <class 'method'>
findtext <class 'method'>
getroot <class 'method'>
iter <class 'method'>
iterfind <class 'method'>
parse <class 'method'>
write <class 'method'>
write_c14n <class 'method'>

其中 getroot() 函式會傳回 XML 文件的根結點, 是一個 Element 物件 : 

>>> root=tree.getroot()    
>>> root 
<Element '顯示卡' at 0x00000214737AB600>
>>> type(root)   
<class 'xml.etree.ElementTree.Element'>

用 dir() 檢視 Element 物件內容 : 

>>> dir(root)   
['__class__', '__copy__', '__deepcopy__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setitem__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'attrib', 'clear', 'extend', 'find', 'findall', 'findtext', 'get', 'insert', 'items', 'iter', 'iterfind', 'itertext', 'keys', 'makeelement', 'remove', 'set', 'tag', 'tail', 'text']

用 list_members() 可以得知哪些是函式哪些是類別 :

>>> list_members(root)   
append <class 'builtin_function_or_method'>
attrib <class 'dict'>
clear <class 'builtin_function_or_method'>
extend <class 'builtin_function_or_method'>
find <class 'builtin_function_or_method'>
findall <class 'builtin_function_or_method'>
findtext <class 'builtin_function_or_method'>
get <class 'builtin_function_or_method'>
insert <class 'builtin_function_or_method'>
items <class 'builtin_function_or_method'>
iter <class 'builtin_function_or_method'>
iterfind <class 'builtin_function_or_method'>
itertext <class 'builtin_function_or_method'>
keys <class 'builtin_function_or_method'>
makeelement <class 'builtin_function_or_method'>
remove <class 'builtin_function_or_method'>
set <class 'builtin_function_or_method'>
tag <class 'str'>
tail <class 'NoneType'>
text <class 'str'>

Element 物件常用屬性如下表 :


 Element 物件常用屬性 說明
 tag 標籤名稱 (值為字串)
 attrib 標籤屬性 (值為字典)
 text 標籤內容 (值為字串)


Element 物件常用方法如下表 :


 Element 物件常用方法 說明
 find(tag) 搜尋標籤名稱 tag, 傳回第一個 Element 物件
 findall(tag) 搜尋標籤名稱 tag, 傳回所有 Element 物件之串列
 iter(tag) 搜尋標籤名稱 tag, 傳回所有 Element 物件之迭代器
 keys() 傳回 Element 物件之全部屬性名稱 (串列)
 items() 傳回 Element 物件之全部屬性名稱與值元組的串列
 get(attr) 傳回 Element 物件之屬性 attr 之值
 set(attr, value) 設定 Element 物件之屬性 attr 之值為 value
 append(subelement) 在目前節點後面加上子節點 subelement
 extend(subelements) 在目前節點後面添加子節點串列 subelements
 remove(element) 刪除子節點物件 element


Element 物件是 XML 文件的基本組成元素, 整個 XML 文件被解析為一個 ElementTree 語法樹,  其最上層節點稱為樹根 (root), 底下由許多 Element 物件節點組成 : 




這跟 BeautifulSoup 將 HTML 文件解析成一棵 DOM 語法樹類似, 樹物件 ElementTree 相當於 BeautifulSoup 物件, 而 Element 物件則類似於 Tag 物件. 關於 BeautifulSoup 套件參考 : 


以上面的顯示卡 XML 文件為例 : 

<?xml version="1.0" encoding="UTF-8"?>
<顯示卡>
  <型號>NVidia RTX3060
    <GPU核心編號>GA106-300</GPU核心編號>
  <CUDA核心數>3584</CUDA核心數>
  <TensorCores>112</TensorCores>
  <VRAM 單位="GB" DDR="DDR6">12G</VRAM>
  <ResizableBAR支援>有</ResizableBAR支援>
  </型號>
</顯示卡> 

>>> import xml.etree.ElementTree as ET   
>>> tree=ET.parse('display_card.xml')   # TreeElement 物件=整棵 XML 語法樹
>>> root=tree.getroot()    # 根節點 : <顯示卡> 標籤

可用 len() 檢查節點有幾個子節點, 每個節點是像串列, 可用 [] 以數字索引取得子節點 :

>>> len(root)   
1
>>> root[0]   
<Element '型號' at 0x0000021473802430>

可見根節點 <顯示卡> 只有 1 個子節點 <型號>, 用 len() 繼續檢查可知它有 5 個子節點 :

>>> len(root[0])   
5  

可以用 2 維索引繼續往下取得下一層節點 :

>>> root[0][0]   
<Element 'GPU核心編號' at 0x0000021473802020>
>>> root[0][1]  
<Element 'CUDA核心數' at 0x00000214738025C0>
>>> root[0][2]  
<Element 'TensorCores' at 0x0000021473802070>
>>> root[0][3]  
<Element 'VRAM' at 0x0000021473802110>
>>> root[0][4]  
<Element 'ResizableBAR支援' at 0x0000021473802390>

用 for 迴圈走訪這 5 個子節點, 並用 tag, attrib, 與 text 屬性取得標籤名稱, 屬性字典, 以及標籤內容 :

>>> for item in root[0]:  
 print(item.tag, item.attrib, item.text)   
                    
GPU核心編號 {} GA106-300
CUDA核心數 {} 3584
TensorCores {} 112
VRAM {'單位': 'GB', 'DDR': 'DDR6'} 12G
ResizableBAR支援 {} 有

可見 5 個子節點中只有 <VRAM> 有屬性, 其它節點的 attrib 屬性值均為空字典. 

除了使用索引走訪 XML 語法樹, 還可以呼叫 Element 物件的 find(), findall(), 與 iter() 等方法來搜尋該節點下的子節點, 例如 root 物件是 <顯示卡>, root[0] 是 <型號>, 呼叫 root[0] 的 find(tag) 方法會傳回 <型號> 下的子節點 (Element 物件) : 

>>> root[0].find('CUDA核心數')   
<Element 'CUDA核心數' at 0x00000214738025C0>
>>> root[0].find('VRAM')     
<Element 'VRAM' at 0x0000021473802110>

find() 的傳回值是第一個符合之 Element 物件, 因此可用 text 屬性取得該物件之內容, 用 attrib 屬性取得其屬性字典 : 

>>> root[0].find('VRAM').text   
'12G'
>>> root[0].find('VRAM').attrib   
{'單位': 'GB', 'DDR': 'DDR6'}

findall() 與 iter() 都是搜尋所有子節點中特定之標籤名稱, 但傳回值不同, findall() 將全部符合之 Element 物件放在串列中傳回; 而 iter() 則是傳回搜尋結果的迭代器 (iterator), 必須用迴圈走訪才能取得這些 Element 物件, 當搜尋結果很龐大時可改用 iter() 來節省 DRAM 耗用情形, 例如搜尋 'VRAM' 標籤 : 

>>> root[0].findall('VRAM')   
[<Element 'VRAM' at 0x0000021473802110>]

左右兩邊有中括號表示傳回值是串列, 且只有一個元素, 可以用索引 0 取得 : 

>>> type(root[0].findall('VRAM'))   
<class 'list'>  
>>> root[0].findall('VRAM')[0]    
<Element 'VRAM' at 0x0000021473802110>
>>> root[0].findall('VRAM')[0].text     
'12G'
>>> root[0].findall('VRAM')[0].attrib    
{'單位': 'GB', 'DDR': 'DDR6'}

Element 物件的 get() 函式則是用來搜尋屬性值, 傳入參數是屬性名稱, 例如 :

>>> root[0].find('VRAM')     
<Element 'VRAM' at 0x0000021473802110
>>> root[0].find('VRAM').get('單位')    # 傳回 VRAM 標籤 '單位' 屬性之值 
'GB'
>>> root[0].find('VRAM').get('DDR')    # 傳回 VRAM 標籤 'DDR' 屬性之值
'DDR6'

下面參考 "xml.etree.ElementTree 筆記" 這篇文章中的 country_data.xml 文件比較 find(), findall(), 與 iter() 這三個方法的差異 :

<?xml version="1.0"?>
<data>
    <country name="Liechtenstein">
        <rank>1</rank>
        <year>2008</year>
        <gdppc>141100</gdppc>
        <neighbor name="Austria" direction="E"/>
        <neighbor name="Switzerland" direction="W"/>
    </country>
    <country name="Singapore">
        <rank>4</rank>
        <year>2011</year>
        <gdppc>59900</gdppc>
        <neighbor name="Malaysia" direction="N"/>
    </country>
    <country name="Panama">
        <rank>68</rank>
        <year>2011</year>
        <gdppc>13600</gdppc>
        <neighbor name="Costa Rica" direction="W"/>
        <neighbor name="Colombia" direction="E"/>
    </country>
</data>

首先用 ElementTree 模組的 parse() 函式讀檔並剖析為 ElementTree 語法樹物件 : 
 
>>> import xml.etree.ElementTree as ET   
>>> tree=ET.parse('country_data.xml')   
>>> root=tree.getroot()   
>>> root  
<Element 'data' at 0x0000021473803650>   

根節點是 <data> 標籤, 它底下有 3 個國家資訊的 <country> 標籤, 呼叫 find() 搜尋 'country' 標籤只能找到第一個國家 Liechtenstein :

>>> root.find('country')  
<Element 'country' at 0x0000021473803330>   
>>> root.find('country').tag   
'country'
>>> root.find('country').get('name')    # 取得屬性 name 之值
'Liechtenstein'

如果使用 findall() 搜尋 'country' 標籤就能找到全部 country 標籤之 Element 物件, 將它們放在串列中傳回來 :

>>> root.findall('country')   
[<Element 'country' at 0x0000021473803330>, <Element 'country' at 0x00000214738030B0>, <Element 'country' at 0x0000021473803920>]  
>>> type(root.findall('country'))   
<class 'list'>

可以用迴圈走訪這個串列 :

>>> for country in root.findall('country'):   
    print(country.attrib)   
                    
{'name': 'Liechtenstein'}
{'name': 'Singapore'}
{'name': 'Panama'}

如果要擷取 XML 文件中的國名, 排行, 與人均 GDP 可以這麼寫 :

>>> for country in root.findall('country'):  
    name=country.get('name')  
    rank=country.find('rank').text  
    gdppc=country.find('gdppc').text  
    print(f'name:{name} rank:{rank} gdppc:{gdppc}')  
                    
name:Liechtenstein rank:1 gdppc:141100
name:Singapore rank:4 gdppc:59900
name:Panama rank:68 gdppc:13600

呼叫 iter() 同樣是搜尋符合之所有子節點, 但傳回一個迭代器 :

>>> root.iter('country')   
<_elementtree._element_iterator object at 0x0000021473828680> 

可以用迴圈走訪迭代器的每個元素 :

>>> for country in root.iter('country'):  
    name=country.get('name')  
    rank=country.find('rank').text  
    gdppc=country.find('gdppc').text  
    print(f'name:{name} rank:{rank} gdppc:{gdppc}')  
                    
name:Liechtenstein rank:1 gdppc:141100
name:Singapore rank:4 gdppc:59900
name:Panama rank:68 gdppc:13600

結果與使用 findall() 相同, 但使用迭代器在資料很大時較不佔用記憶體, 關於迭代器參考 :



2. 呼叫 ElementTree 類別的建構式剖析 XML 文件 : 

第二種剖析 XML 文件的方法是呼叫 xml.etree.ElementTree.ElementTree 類別的建構函式  ElementTree() 並傳入 .xml 檔之路徑與檔名, 它會傳回一個 ElementTree 物件 :

>>> import xml.etree.ElementTree as ET   

以上面的顯示卡 XML 文件 display_card.xml 為例, 呼叫 ElementTree() 並傳入此 XML 文件就會讀取它並將其剖析為一個 XML 語法樹 :

>>> tree=ET.ElementTree(file='display_card.xml')   
>>> type(tree)    
<class 'xml.etree.ElementTree.ElementTree'>  
>>> root=tree.getroot()   
>>> type(root)  
<class 'xml.etree.ElementTree.Element'>
>>> root  
<Element '顯示卡' at 0x0000021473802160>  
>>> root.tag  
'顯示卡'

其它操作均與上面 parse() 相同. 


3. 呼叫 fromstring() 函式剖析 XML 文件 : 

fromstring() 函式用來剖析 XML 字串, 首先將上面 display_card.xml 的內容放進一個長字串 : 

>>> xml_str='''<?xml version="1.0" encoding="UTF-8"?>
<顯示卡>
  <型號>NVidia GTX3060
    <GPU核心編號>GA106-300</GPU核心編號>
<CUDA核心數>3584</CUDA核心數>
<TensorCores>112</TensorCores>
<VRAM 單位="GB" DDR="DDR6">12G</VRAM>
<ResizableBAR支援>有</ResizableBAR支援>
  </型號>
</顯示卡>'''
>>> xml_str  
'<?xml version="1.0" encoding="UTF-8"?>\n<顯示卡>\n  <型號>NVidia GTX3060\n    <GPU核心編號>GA106-300</GPU核心編號>\n\t<CUDA核心數>3584</CUDA核心數>\n\t<TensorCores>112</TensorCores>\n\t<VRAM 單位="GB" DDR="DDR6">12G</VRAM>\n\t<ResizableBAR支援>有</ResizableBAR支援>\n  </型號>\n</顯示卡>'

然後將此常字串傳給 ElementTree 模組的 fromstring() 函式. 注意, 與呼叫 parse() 不同的是, 它在剖析完成後傳回 XML 語法樹的根節點 Element 物件, 而非像 parse() 那樣傳回 ElementTree 物件 (整棵語法樹) : 

>>> root=ET.fromstring(xml_str)     
>>> type(root)   
<class 'xml.etree.ElementTree.Element'>   
>>> root  
<Element '顯示卡' at 0x0000021473802ED0>

其它操作均與上面 parse() 相同. 

當然也可以用 with open() 指定 utf-8 編碼格式開啟 .xml 檔, 呼叫 read() 讀取檔案內容字串之後再傳給 fromstring() 剖析為 XML 語法樹, 例如 : 

>>> with open('display_card.xml', 'r', encoding='utf-8') as f:   
    root=ET.fromstring(f.read())   
                    
>>> root   
<Element '顯示卡' at 0x0000021473803740>


二. 使用 XPath 搜尋節點 : 

XPath (XML Path Language) 是一種小型的路徑表達式, 用在呼叫 findall() 或 iter() 時於 XML 文件語法樹中搜尋目標節點. XPath 採用類似於檔案路徑的描述, 例如 /A/B/C 表示找尋根節點的子節點下的 C 節點, 而 A//B/*[1] 表示搜尋目前節點下的 A 節點下不論幾層下的子孫節點 B 的任何標籤名稱的第一個節點, 參考 :


不過 xml 套件並沒有支援全部的 XPath, 僅支援如下常用語法 : 


 xml 套件支援的 XPath 語法 說明
 tag 搜尋標籤名稱為 tag 的子節點
 * 所有子節點
 . 目前節點
 // 全部子孫節點
 .. 父節點 (上一層)
 [@attr] 搜尋全部含有屬性 attr 的節點
 [@attr='value'] 搜尋全部含有屬性 attr, 且值為 value 的節點
 [tag] 搜尋標籤名稱為 tag 的所有子節點
 [tag='text'] 搜尋全部子孫節點中標籤名稱為 tag, 且內容為 text 的所有子節點
 [index] 搜尋位置索引 (1 起始) 為 index 的子節點


以上面的 country_data.xml 文件為例 : 


>>> import xml.etree.ElementTree as ET   
>>> tree=ET.parse('country_data.xml')   
>>> root=tree.getroot()   
>>> root  
<Element 'data' at 0x0000021473803650>   

呼叫 findall() 時傳入 "." 表示要尋找目前節點 : 

>>> root.findall(".")   
[<Element 'data' at 0x0000021473803650>]

可見目前是在根節點 <data> 下. 傳入 "./country/neighbor" 表示要尋找目前節點下的 country 下的全部 neighbor 節點 :

>>> root.findall("./country/neighbor")   
[<Element 'neighbor' at 0x0000021473802CA0>, <Element 'neighbor' at 0x0000021473803970>, <Element 'neighbor' at 0x00000214738036F0>, <Element 'neighbor' at 0x0000021473803BA0>, <Element 'neighbor' at 0x0000021473803B50>]

可見 5 個 <neighbor> 節點都找到了. 傳入 ".//neighbor" 表示要搜尋語法樹中的所有 <neighbor> 標籤, ".//" 表示目前節點 <data> 的所有子孫節點 :

>>> root.findall(".//neighbor")  
[<Element 'neighbor' at 0x0000021473802CA0>, <Element 'neighbor' at 0x0000021473803970>, <Element 'neighbor' at 0x00000214738036F0>, <Element 'neighbor' at 0x0000021473803BA0>, <Element 'neighbor' at 0x0000021473803B50>]

同樣是這 5 個 <neighbor> 節點, 但意思與上面的 "./country/neighbor" 不同. 

傳入 "*[@name]" 表示要搜尋所有子節點 "*" 中含有 name 屬性者 : 

>>> root.findall("*[@name]")    
[<Element 'country' at 0x0000021473803330>, <Element 'country' at 0x00000214738030B0>, <Element 'country' at 0x0000021473803920>]

只找出 3 個 <country> 標籤, <neighbor> 節點雖然也有 name 屬性, 因為 <neighbor> 是孫節點, 而 "*" 表示只抓所有子節點而已. 

如果要抓全部子孫節點中含有 name 屬性者, 要傳入 ".//*[@name]", 其中 ".//" 表示現在節點下的所有子孫, "*[@name]" 表示含有 name 屬性的子節點:

>>> root.findall(".//*[@name]")   
[<Element 'country' at 0x0000021473803330>, <Element 'neighbor' at 0x0000021473802CA0>, <Element 'neighbor' at 0x0000021473803970>, <Element 'country' at 0x00000214738030B0>, <Element 'neighbor' at 0x00000214738036F0>, <Element 'country' at 0x0000021473803920>, <Element 'neighbor' at 0x0000021473803BA0>, <Element 'neighbor' at 0x0000021473803B50>]
>>> len(root.findall(".//*[@name]"))
8

可見連同 <country> 與 <neighbor> 共有 8 個標籤含有 name 屬性. 

傳入 ".//*[@name='Panama']/neighbor" 表示要搜尋目前節點的全部子孫中含有 name='Panama' 屬性的 <neighbor> 子節點, 這會找到兩個 :

>>> root.findall(".//*[@name='Panama']/neighbor")  
[<Element 'neighbor' at 0x0000021473803BA0>, <Element 'neighbor' at 0x0000021473803B50>]


三. 操控 XML 語法樹與寫入 .xml 檔 : 

Element 物件的 get(attr) 可以取得屬性 attr 之值, set(attr, value) 則可設定其值, 例如 : 

>>> import xml.etree.ElementTree as ET   
>>> tree=ET.parse('display_card.xml')   
>>> root=tree.getroot()   
>>> root     
<Element '顯示卡' at 0x0000021473828540>   

用索引取得根節點 <顯示卡> 下第一個子節點 <型號>, 由 attr 屬性為空字典可知它原本是沒有屬性的, 呼叫 set() 幫它設定一個屬性 "重量" 後就有了 : 

>>> root[0]     
<Element '型號' at 0x0000021473828DB0>   
>>> root[0].attr  
{}  
>>> root[0].set("重量", "996g")    
>>> root[0].get("重量")   
'996g'  
>>> root[0].attrib  
{'重量': '996g'}

呼叫 keys 會把全部屬性名稱 (key) 以串列傳回, 呼叫 items() 則會把 (屬性, 值) 以串列傳回 : 

>>> root[0].keys()   
['重量']
>>> root[0].items()   
[('重量', '996g')]

呼叫 append() 可以將一個節點貼附到目前節點後面, 首先用 ElementTree 的 Element 類別來建立一個節點, 只要呼叫其建構式 Element() 並傳入標籤名稱即可, 例如欲在 RTX3060 顯示卡的 XML 文件上附加 <功耗>170W</功耗> 節點 :

>>> power=ET.Element("功耗")   # 呼叫建構式傳入標籤名稱
>>> power   
<Element '功耗' at 0x000002147384C1D0>   
>>> power.text="170W"    # 設定標籤內容
>>> power.set("單位", "瓦")     # 設定屬性
>>> power.items()     # 傳回屬性串列
[('單位', '瓦')]   

然後呼叫 <型號> 標籤的 Element 物件 (即 root[0]) 的 append() 方法將此 <功耗> 標籤加到最後一個子節點後面 :

>>> root.find('型號').append(power)    # 用 root[0].append(power) 也可以 
>>> len(root[0].findall('.//'))     # <型號> 的子節點多了 1 個
6
>>> root[0].findall('.//')  
[<Element 'GPU核心編號' at 0x0000021473828630>, <Element 'CUDA核心數' at 0x000002147382B5B0>, <Element 'TensorCores' at 0x0000021473828220>, <Element 'VRAM' at 0x0000021473828360>, <Element 'ResizableBAR支援' at 0x0000021473803010>, <Element '功耗' at 0x000002147384C1D0>]

可見多出了新增的 <功耗> 子節點.

將此修改過的 ElementTree 物件 tree 寫到新的 ,xml 檔, 這可以透過呼叫 ElementTree 物件的 write() 方法並指定 encoding='utf-8' 屬性即可將整棵 XML 語法樹寫入 .xml 檔, 語法如下 : 

tree.write("路徑檔名", encoding="utf-8")  

>>> tree.write('display_card_1.xml', encoding='utf-8')   

這時開啟 display_card_1.xml 檔 : 

<顯示卡>
  <型號>NVidia GTX3060
    <GPU核心編號>GA106-300</GPU核心編號>
<CUDA核心數>3584</CUDA核心數>
<TensorCores>112</TensorCores>
<VRAM 單位="GB" DDR="DDR6">12G</VRAM>
<ResizableBAR支援>有</ResizableBAR支援>
  <功耗 單位="瓦">170W</功耗></型號>
</顯示卡>

可見 <型號> 確實多了一個子節點 <功耗>, 

新增節點也可以使用 SubElement 類別, 呼叫其建構式 SubElement(element, tag) 即可在 element 節點後面添加子節點 <tag>, 由於建構式第一參數已指定父節點物件, 因此不需要使用父節點的 append() 方法來添加, 例如 : 

>>> hdcp=ET.SubElement(root[0], 'HDCP支援')     # 在 <型號> 節點下建立子節點
>>> hdcp.text='有'    # 設定子節點內容
>>> hdcp   
<Element 'HDCP支援' at 0x000002147475E1B0>    
>>> root[0].findall('.//')    
[<Element 'GPU核心編號' at 0x0000021473828630>, <Element 'CUDA核心數' at 0x000002147382B5B0>, <Element 'TensorCores' at 0x0000021473828220>, <Element 'VRAM' at 0x0000021473828360>, <Element 'ResizableBAR支援' at 0x0000021473803010>, <Element '功耗' at 0x000002147384C1D0>, <Element 'HDCP支援' at 0x000002147475E1B0>]

將目前的語法樹寫入 display_card_2.xml 檔 :

>>> tree.write('display_card_2.xml', encoding='utf-8')   

開啟此檔案內容如下 :

<顯示卡>
  <型號>NVidia GTX3060
    <GPU核心編號>GA106-300</GPU核心編號>
<CUDA核心數>3584</CUDA核心數>
<TensorCores>112</TensorCores>
<VRAM 單位="GB" DDR="DDR6">12G</VRAM>
<ResizableBAR支援>有</ResizableBAR支援>
  <功耗 單位="瓦">170W</功耗><HDCP支援>有</HDCP支援></型號>
</顯示卡>

可見 <型號> 節點下已新增了一個子節點 <HDCP支援>. 

另外一個可添加子節點的是 extends(elements) 方法, 與 append() 方法一次只能添加一個子節點不同的是, 它可一次添加多個子節點, 其傳入值為 Element 物件之串列, 裡面可以放多個要添加的子節點 :  

先用 ET.Element() 建構式建立兩個子節點 :

>>> directx=ET.Element("DIRECTX支援版本")   
>>> directx.text="12"   
>>> opengl=ET.Element("OPENGL支援版本")   
>>> opengl.text="4.6"   

再將此兩子節點放入串列或元組中傳給父節點的 extend() 方法將其加入父節點中 :

>>> root[0].extend([directx, opengl])   
>>> root[0].findall('.//')   
[<Element 'GPU核心編號' at 0x0000021473828630>, <Element 'CUDA核心數' at 0x000002147382B5B0>, <Element 'TensorCores' at 0x0000021473828220>, <Element 'VRAM' at 0x0000021473828360>, <Element 'ResizableBAR支援' at 0x0000021473803010>, <Element '功耗' at 0x000002147384C1D0>, <Element 'HDCP支援' at 0x000002147475E1B0>, <Element 'DIRECTX支援版本' at 0x000002147475E160>, <Element 'OPENGL支援版本' at 0x000002147475E070>]

將目前的語法樹寫入 display_card_3.xml 檔 :

>>> tree.write('display_card_3.xml', encoding='utf-8')   

開啟檔案檢視內容 :

<顯示卡>
  <型號>NVidia GTX3060
    <GPU核心編號>GA106-300</GPU核心編號>
<CUDA核心數>3584</CUDA核心數>
<TensorCores>112</TensorCores>
<VRAM 單位="GB" DDR="DDR6">12G</VRAM>
<ResizableBAR支援>有</ResizableBAR支援>
  <功耗 單位="瓦">170W</功耗><HDCP支援>有</HDCP支援><DIRECTX支援版本>12</DIRECTX支援版本><OPENGL支援版本>4.6</OPENGL支援版本></型號>
</顯示卡>

可見這兩個子節點已加入至父節點 <型號> 內. 

最後來測試 Element 物件的 remove(element) 方法, 它可刪除傳入之子節點, 接續上面結果, 若要刪除 <功耗> 與 <HDCP支援> 子節點, 可以用 find() 搜尋後傳入 remove() : 

>>> root[0].remove(root[0].find('功耗'))   
>>> root[0].remove(root[0].find('HDCP支援'))      
>>> root[0].findall('.//')   
[<Element 'GPU核心編號' at 0x0000021473828630>, <Element 'CUDA核心數' at 0x000002147382B5B0>, <Element 'TensorCores' at 0x0000021473828220>, <Element 'VRAM' at 0x0000021473828360>, <Element 'ResizableBAR支援' at 0x0000021473803010>, <Element 'DIRECTX支援版本' at 0x000002147475E160>, <Element 'OPENGL支援版本' at 0x000002147475E070>]

將 tree 寫入檔案 : 

>>> tree.write('display_card_4.xml', encoding='utf-8')    

開啟 display_card_4.xml 內容如下 :

<顯示卡>
  <型號>NVidia GTX3060
    <GPU核心編號>GA106-300</GPU核心編號>
<CUDA核心數>3584</CUDA核心數>
<TensorCores>112</TensorCores>
<VRAM 單位="GB" DDR="DDR6">12G</VRAM>
<ResizableBAR支援>有</ResizableBAR支援>
  <DIRECTX支援版本>12</DIRECTX支援版本><OPENGL支援版本>4.6</OPENGL支援版本></型號>
</顯示卡>

確認這兩個子節點已被刪除. 

參考 : 


2024 年第 16 周記事

上週日將鄉下老家那台開不了機的舊 PC (二哥國高中時玩遊戲改裝的) 載來高雄, 但一直拖到周六才拿去順發檢修, 帥哥工程師檢查後發現應該是其中兩排有散熱片的 DDR3 記憶體有問題所致, 另兩排金士頓的一個 OK, 另一個 NG, 因為有終生保固, 所以幫我送原廠更換. 另外 ASUS 獨顯無輸出判定應該是壞了, 並建議我買新的機殼, 他幫我挑了一個 1090 元有 LED 的, 先付機殼的錢後我就先回家了. 今天下班後去取貨, 帥哥已經幫我先安裝 Win10 隨機版 (CPU 無法上 Win11), 先用那排 OK 的 4GB DRAM 跑 (目前插在 CPU 旁起算第二排), 等金士頓保固送回來再插第一排即可 (優先是抓第二排). 檢修與裝機費合計 1000 元, 花 2100 救回一台電腦也還 OK, 當備援的下載機. 

鄉下的小灰周二生出三隻小貓咪, 原先是在車庫鐵架後面冷氣下方的小空間養子, 但周六一隻隻刁到庫房門口椅子下的紙箱內, 我週六下午回到家趁小灰不在把其中一隻抓出來照相 :







不過當她聽到小貓仔喵喵叫走過來, 雖然只在旁邊看沒阻止我, 但過一會兒她就把他們一隻隻又刁走, 後來發現是移到庫房裡面的角落了, 可能是怕我偷她小孩, 哈哈. 

水某週五去新加坡開會, 姊姊與菁菁原本要跟, 但姐姐周六要出差回高雄, 因她老闆接到藥廠臨床試驗研討會的案子, 要在義享天地的萬豪酒店舉辦, 所以只好取消, 這回水某自己一個人去 (我陪她去也是可以, 但新加坡太小, 1.5 天也不知要去哪玩, 花機票錢不值得). 不過時間也搭配得剛剛好, 水某週四晚上去台北住姐姐那裡一晚, 週五水某去搭機, 姊姊回高雄. 

本周看完 Netflix 的 "三體", 這原先是上次二哥回來掃墓時說想看第一集就好, 聽說此集有對文化大革命批鬥之寫實描述, 上週看完寄生獸接著就把三體也看完. 劇中那個遊戲頭盔好特別好時尚, 沒充電孔是透過無線充電嗎? 三體人的科技令人恐懼 (所以霍金才說別跟外星人有連繫). 淚之女神來到尾聲越來越精彩, 癡情又奸詐的尹殷盛為了讓洪海仁手術醒來失去記憶時第一時間看到他, 竟然偽造白賢祐殺害片姓男子證據, 導致白賢祐在海仁甦醒前遭到國際刑警逮捕, 這個周末將迎來完結篇, 期待白女婿的逆轉大作戰.