🕵️‍♂️ Python Regex 實戰:如何精準拆解 CamelCase,同時完美「保護」您的專有名詞?(?i:) 非捕獲與忽略大小寫

加入好友
加入社群
🕵️‍♂️ Python Regex 實戰:如何精準拆解 CamelCase,同時完美「保護」您的專有名詞?(?i:) 非捕獲與忽略大小寫 - 儲蓄保險王

在進行文字探勘或處理資料時,我們常常需要把類似 NVMeProcessStep 這樣的 CamelCase(駝峰式命名)拆解成 [‘NVMe’, ‘Process’, ‘Step’]。

普通的正規表達式(Regex)很容易發生「過度切割」的慘劇,例如把 NVMe 切成 [‘NV’, ‘Me’],導致後續的比對完全失準。這篇文章將教你如何利用 Raw String (r) 與 Regex 內聯修飾符 ((?i:)),替專有名詞打造絕對防禦的護城河!

🛑 痛點:過度切割的駝峰命名
假設我們有一個負責拆解駝峰單字的基礎規則(Base Pattern):
它會尋找連續的大寫或小寫字母進行切割。

import re

# 基礎拆字規則 (能應付大部分駝峰命名)
base_pattern = r'[A-Z]?[a-z]+|[A-Z]+(?=[A-Z][a-z]|\d|\W|$)|\d+'

print(re.findall(base_pattern, "NormalProcessStep")) 
# ✅ 輸出: ['Normal', 'Process', 'Step'] (完美)

print(re.findall(base_pattern, "NVMeProcessStep"))   
# ❌ 輸出: ['NV', 'Me', 'Process', 'Step'] (悲劇NVMe 被切碎了)
🕵️‍♂️ Python Regex 實戰:如何精準拆解 CamelCase,同時完美「保護」您的專有名詞?(?i:) 非捕獲與忽略大小寫 - 儲蓄保險王

看到問題了嗎?對於包含大小寫交錯的科技專有名詞(如 NVMe, PCIe, I2c),基礎的駝峰規則會將它們破壞。我們需要建立一個例外保護清單 (Exceptions Whitelist)。

🛡️ 解法:建立「無視大小寫強勢攔截」的防護罩
為了解決這個問題,標準的做法是把「例外清單」放在正則表達式的最前面,而且為了方便維護,我們通常會用陣列來管理這些詞:

# 1. 定義要保護的專有名詞 (簡化版)
exceptions = [r'NVMe', r'PCIe', r'I2c']

# 2. 將它們用 ' | ' (OR) 串接起來
exceptions_joined = '|'.join(exceptions) 
# 結果會是: 'NVMe|PCIe|I2c'

關鍵魔法:(?i:) 非捕獲與忽略大小寫
如果我們只把字串接起來還不夠,
萬一資料裡寫的是 nvme 或 PCIE 怎麼辦?
又或者 Regex 引擎在抓取時,因為括號的關係產生了多餘的陣列階層?

這時我們就要替它套上 (?i: … ) 外衣:

?i (Ignore case):讓括號內的所有單字無視大小寫。
: (Non-capturing):告訴 re.findall:「這只是用來群組比對的條件,
不要把裡面的內容單獨抽出來當作獨立的結果」。
加上 r (Raw String,防止字串內的反斜線被錯誤轉譯) 後,
我們就能合成終極的保護規則:

# 3. 穿上防護外衣
exceptions_pattern = r'(?i:' + exceptions_joined + r')'
# 最終變成: r'(?i:NVMe|PCIe|I2c)'

🚀 完整範例:見證奇蹟的時刻

讓我們把「例外保護罩」與「基礎拆字規則」結合,只要用 | 把兩者接起來,把例外放前面,Regex 就會「優先」攔截專有名詞!

import re

# --- 1. 準備規則 ---
exceptions = [r'NVMe', r'PCIe', r'I2c']
exceptions_pattern = r'(?i:' + '|'.join(exceptions) + r')'
base_pattern = r'[A-Z]?[a-z]+|[A-Z]+(?=[A-Z][a-z]|\d|\W|$)|\d+'

# 將例外規則放在最前面擁有最高優先權
# 最終的正則會長這樣r'(?i:NVMe|PCIe|I2c)|[A-Z]?[a-z]+|[A-Z]+(?=...)'
camel_case_pattern = re.compile(exceptions_pattern + r'|' + base_pattern)

# --- 2. 測試實際的 class_name ---
test_classes = [
    "NVMeProcessStep",       # 測試大小寫混雜的專有名詞
    "PCIeConfigCheck",       # 測試包含 e 的專有名詞
    "I2ctransferTest",       # 測試帶有數字的專有名詞
    "NormalPowerStatus"      # 測試完全沒有專有名詞的一般駝峰
]

for class_name in test_classes:
    tokens = camel_case_pattern.findall(class_name)
    print(f"{class_name.ljust(20)} -> {tokens}")

💡 執行結果:

🕵️‍♂️ Python Regex 實戰:如何精準拆解 CamelCase,同時完美「保護」您的專有名詞?(?i:) 非捕獲與忽略大小寫 - 儲蓄保險王

完美! NVMe 和 PCIe 沒有被切斷,I2c 也被成功獨立辨識出來。

📝 總結

  1. Raw String (r'...'):寫 Regex 時永遠養成加上 r 的好習慣,保持程式碼乾淨。
  2. 例外優先原則:拆解複雜字串時,把「不想被切壞」的詞透過 | 放在正規表達式的最前面攔截。
  3. (?i:) 群組法:動態組裝 Regex 時,用它包覆陣列,就能輕鬆達成「局部忽略大小寫」且「不產生多餘捕獲物」的優雅設計!

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

「配對到 (Match)」和「捕獲 (Capture)」在 Regex 裡面是兩件完全不同的事!

NVMe 之所以能出現在陣列裡,是因為它被規則「配對 (Match) 到了整個字串」,而不是被「捕獲」出來的。

我們用程式碼來實際「撞牆」一次,你馬上就會秒懂為什麼要叫「非捕獲」了:

當 re.findall 遇到「普通括號 (捕獲群組)」會發生的災難
在 Python 裡,re.findall 有一個非常霸道的隱藏設定:「只要我的規則裡面有正常的括號 (),我就『只』吐出括號裡面抓到的東西,其他符合條件的我全部變空字串或丟掉!」

我們故意寫一個錯誤的、沒有加 ?: 的一般括號版本:

import re

# ⚠️ 錯誤示範使用普通括號 (捕獲群組) 來包裝 exceptions
bad_pattern = r'(NVMe|PCIe)|[A-Z]?[a-z]+'

print(re.findall(bad_pattern, "NVMeProcessStep"))

❌ 實際輸出的災難結果:

🕵️‍♂️ Python Regex 實戰:如何精準拆解 CamelCase,同時完美「保護」您的專有名詞?(?i:) 非捕獲與忽略大小寫 - 儲蓄保險王

發生了什麼事?

第一步切到了 NVMe,因為它在括號裡,所以成功吐出 ‘NVMe’。
第二步切到了 Process,它是被右邊的 [A-Z]?[a-z]+ 規則找到的,並沒有進入左邊的括號裡。結果 re.findall 看括號裡沒東西,就無視了 Process,直接吐出空字串 ”。
第三步 Step 也是一樣,吐出 ”。
這就是「捕獲 (Capture)」的副作用!它會把陣列的結構徹底搞爛。

錯誤示範 2:雙邊捕獲群組 (產生 Tuple 海)
如果你想把兩邊都放進括號:r'(NVMe|PCIe)|([A-Z]?[a-z]+)’

# ⚠️ 毀滅級錯誤改變了資料型態
bad_pattern_2 = r'(NVMe|PCIe)|([A-Z]?[a-z]+)'
print(re.findall(bad_pattern_2, "NVMeProcessStep"))
# ❌ 輸出結果: [('NVMe', ''), ('', 'Process'), ('', 'Step')]

發生了什麼事? 只要有超過一個以上的括號,findall 就會開始吐出 Tuple(元組)!你原本期待得到單純的字串清單來做後續的 .lower() 轉小寫,現在拿到一堆 Tuple,程式直接報錯當機。

🕵️‍♂️ Python Regex 實戰:如何精準拆解 CamelCase,同時完美「保護」您的專有名詞?(?i:) 非捕獲與忽略大小寫 - 儲蓄保險王

解除霸道設定:加入 ?: 或 ?i: (非捕獲群組)
為了解決這個災難,Regex 發明了 (?:) (非捕獲群組)。
它的意思是警告 re.findall:「嘿!這個括號只是用來把字綁在一起做 OR (|) 的範圍而已,你給我乖乖回傳『一整段配對到的字』,不要在那邊只挑括號裡面的東西回傳!」

# ✅ 正確示範使用 ?: (非捕獲群組)
good_pattern = r'(?:NVMe|PCIe)|[A-Z]?[a-z]+'

print(re.findall(good_pattern, "NVMeProcessStep"))

✅ 實際輸出的完美結果:

🕵️‍♂️ Python Regex 實戰:如何精準拆解 CamelCase,同時完美「保護」您的專有名詞?(?i:) 非捕獲與忽略大小寫 - 儲蓄保險王

總結

  • 配對 (Match) = 規則成功掃描到的字。(我們需要它)
  • 捕獲 (Capture) = 用普通括號 () 觸發的特殊抽取機制,會破壞 findall 的預設輸出列。(我們不要它)

所以我們才稱 (?:) 為**「非捕獲」**群組,為了就是壓制括號的特殊機制,讓 NVMeProcessStep 都能平等地被「配對」出來變成陣列元素」吐出來!

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

加入好友
加入社群
🕵️‍♂️ Python Regex 實戰:如何精準拆解 CamelCase,同時完美「保護」您的專有名詞?(?i:) 非捕獲與忽略大小寫 - 儲蓄保險王

儲蓄保險王

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

You may also like...

發佈留言

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