Python 正規表示式入門:捕獲組() vs 非捕獲組(?:) vs 命名捕獲組(?P<name>)

加入好友
加入社群
Python 正規表示式入門:捕獲組() vs 非捕獲組(?:) vs 命名捕獲組(?P<name>) - 儲蓄保險王

在 Python 正規表示式中,圓括號 () 有兩種常見用途:

  1. 捕獲組 Capturing Group
  2. 非捕獲組 Non-capturing Group

兩者都可以用來「把一段正規表示式包起來」,但差別在於:

捕獲組會保存匹配結果;非捕獲組只負責分組,不保存結果。


1. 基本語法

捕獲組

(...)
# 例如:
r"(cat|dog)"
# 表示匹配cat  dog

而且會把匹配到的 cat 或 dog 保存起來。


非捕獲組

(?:...)
# 例如
r"(?:cat|dog)"
# 同樣表示匹配cat  dog

但不會額外保存 cat 或 dog


2. 最簡單的比較

以下程式碼可以直接貼到 Jupyter Notebook 執行:

import re

text = "cat dog bird"

capture_pattern = r"(cat|dog)"
non_capture_pattern = r"(?:cat|dog)"

capture_result = re.findall(capture_pattern, text)
non_capture_result = re.findall(non_capture_pattern, text)

print("捕獲組結果:", capture_result)
print("非捕獲組結果:", non_capture_result)

輸出:

Python 正規表示式入門:捕獲組() vs 非捕獲組(?:) vs 命名捕獲組(?P<name>) - 儲蓄保險王

這個例子中,兩者結果看起來一樣,因為整個分組內容就是完整匹配內容。

差異要在搭配其他內容時才會明顯。


3. 搭配 .txt 看差異

import re

text = "cat.txt dog.txt bird.txt"

capture_pattern = r"(cat|dog)\.txt"
non_capture_pattern = r"(?:cat|dog)\.txt"

capture_result = re.findall(capture_pattern, text)
non_capture_result = re.findall(non_capture_pattern, text)

print("捕獲組結果:", capture_result)
print("非捕獲組結果:", non_capture_result)

輸出:

Python 正規表示式入門:捕獲組() vs 非捕獲組(?:) vs 命名捕獲組(?P<name>) - 儲蓄保險王

為什麼結果不同?

捕獲組 pattern:

(cat|dog)\.txt

可以拆成:

(cat|dog)  → 捕獲 cat  dog
\.txt匹配 .txt

完整匹配是:
cat.txt
但捕獲組只保存:
cat
所以 findall() 回傳:

['cat', 'dog']

非捕獲組 pattern:

(?:cat|dog)\.txt

其中:
(?:cat|dog)
只負責分組,不保存內容。

因此 findall() 沒有捕獲組可回傳,就回傳完整匹配:

['cat.txt', 'dog.txt']

4. findall() 的回傳規則

re.findall() 會根據捕獲組數量決定回傳格式。

沒有捕獲組

import re

text = "cat.txt dog.txt"

result = re.findall(r"(?:cat|dog)\.txt", text)

print(result)

輸出:

Python 正規表示式入門:捕獲組() vs 非捕獲組(?:) vs 命名捕獲組(?P<name>) - 儲蓄保險王

有一個捕獲組

import re

text = "cat.txt dog.txt"

result = re.findall(r"(cat|dog)\.txt", text)

print(result)

輸出:

Python 正規表示式入門:捕獲組() vs 非捕獲組(?:) vs 命名捕獲組(?P<name>) - 儲蓄保險王

有兩個捕獲組

import re

text = "cat.txt dog.txt"

result = re.findall(r"(cat|dog)(\.txt)", text)

print(result)
Python 正規表示式入門:捕獲組() vs 非捕獲組(?:) vs 命名捕獲組(?P<name>) - 儲蓄保險王

因為有兩個捕獲組:

(cat|dog)  →  1 
(\.txt)    →  2 

所以每個匹配結果會變成 tuple。


規則整理

Python 正規表示式入門:捕獲組() vs 非捕獲組(?:) vs 命名捕獲組(?P<name>) - 儲蓄保險王

5. 使用 group() 觀察完整匹配與捕獲組

使用 re.search() 時,可以透過 group() 取得不同內容。

import re

text = "cat.txt dog.txt"

pattern = r"(cat|dog)\.txt"

match = re.search(pattern, text)

print("group(0):", match.group(0))
print("group(1):", match.group(1))
print("groups():", match.groups())

輸出:

Python 正規表示式入門:捕獲組() vs 非捕獲組(?:) vs 命名捕獲組(?P<name>) - 儲蓄保險王

group(0)

match.group(0)

表示:

整個正規表示式匹配到的內容。

結果:
cat.txt

group(1)

match.group(1)

表示:

第 1 個捕獲組的內容。

結果:
cat

groups()

match.groups()

表示:

所有捕獲組組成的 tuple。

結果:

('cat',)

6. 非捕獲組仍然可以控制範圍

非捕獲組雖然不保存結果,但仍然非常重要。

例如:

import re

text = "cat.txt dog.txt bird.py"

pattern = r"(?:cat|dog)\.txt"

result = re.findall(pattern, text)

print(result)
Python 正規表示式入門:捕獲組() vs 非捕獲組(?:) vs 命名捕獲組(?P<name>) - 儲蓄保險王

這裡:

(?:cat|dog)

的功能是讓 | 只作用於:

cat
dog

後面的:

\.txt

則必須跟在兩者後面。


如果不分組,意義會不同

import re

text = "cat cat.txt dog.txt"

pattern = r"cat|dog\.txt"

result = re.findall(pattern, text)

print(result)
Python 正規表示式入門:捕獲組() vs 非捕獲組(?:) vs 命名捕獲組(?P<name>) - 儲蓄保險王

因為:

cat|dog\.txt

實際意思是:
cat 或 dog.txt
不是:
cat.txt 或 dog.txt
正確寫法是:

pattern = r"(?:cat|dog)\.txt"

7. 捕獲組的用途

捕獲組適合用在:

  • 取出日期的年、月、日;
  • 取出檔名與副檔名;
  • 解析 URL;
  • 解析設定文字;
  • 擷取錯誤訊息中的代碼;
  • 透過 group() 取得特定欄位。

解析日期

import re

text = "Today is 2025-09-18."

pattern = r"(\d{4})-(\d{2})-(\d{2})"

match = re.search(pattern, text)

print("完整日期:", match.group(0))
print("年份:", match.group(1))
print("月份:", match.group(2))
print("日期:", match.group(3))
Python 正規表示式入門:捕獲組() vs 非捕獲組(?:) vs 命名捕獲組(?P<name>) - 儲蓄保險王

8. 非捕獲組的用途

非捕獲組適合用在:

  • 控制 | 的範圍;
  • 對整組使用 ?+*
  • 不需要保存中間結果;
  • 避免影響捕獲組編號;
  • 只想取得完整匹配文字。

匹配不同副檔名

import re

text = "report.txt image.png data.csv script.py"

pattern = r"\w+\.(?:txt|png|csv)"

result = re.findall(pattern, text)

print(result)
Python 正規表示式入門:捕獲組() vs 非捕獲組(?:) vs 命名捕獲組(?P<name>) - 儲蓄保險王

這裡:

(?:txt|png|csv)

只用來表示副檔名的選項,不需要分別捕獲:
txt png csv

因此使用非捕獲組。


9. 捕獲組與非捕獲組可以混合使用

實務上常常只捕獲需要的資料,其他分組使用非捕獲組。

import re

text = "report.txt image.png data.csv"

pattern = r"(\w+)\.(?:txt|png|csv)"

result = re.findall(pattern, text)

print(result)
Python 正規表示式入門:捕獲組() vs 非捕獲組(?:) vs 命名捕獲組(?P<name>) - 儲蓄保險王

這裡:

(\w+)

是捕獲組,因為要取得檔名。

(?:txt|png|csv)

是非捕獲組,因為只需要限制副檔名,不需要取出副檔名。


10. 命名捕獲組

當正規表示式較複雜時,建議使用命名捕獲組:

(?P<名稱>...)

例如:

import re

text = "2025-09-18"

pattern = (
    r"(?P<year>\d{4})-"
    r"(?P<month>\d{2})-"
    r"(?P<day>\d{2})"
)

match = re.search(pattern, text)

print("完整日期:", match.group(0))
print("年份:", match.group("year"))
print("月份:", match.group("month"))
print("日期:", match.group("day"))
print("字典形式:", match.groupdict())
Python 正規表示式入門:捕獲組() vs 非捕獲組(?:) vs 命名捕獲組(?P<name>) - 儲蓄保險王

命名捕獲組比數字編號更容易閱讀:

match.group("year")

通常比:

match.group(1)

更清楚。


11. 什麼時候使用捕獲組?

如果你要從匹配結果中「取出某一部分」,使用捕獲組。

例如:

import re

text = "ERROR: E104 connection reset"

pattern = r"ERROR: (E\d+) (.+)"

match = re.search(pattern, text)

print("錯誤代碼:", match.group(1))
print("錯誤訊息:", match.group(2))
Python 正規表示式入門:捕獲組() vs 非捕獲組(?:) vs 命名捕獲組(?P<name>) - 儲蓄保險王

12. 什麼時候使用非捕獲組?

如果你只需要完整匹配,不需要中間結果,就使用非捕獲組。

例如:

import re

text = "https://example.com http://example.org"

pattern = r"https?://\S+"

result = re.findall(pattern, text)

print(result)
Python 正規表示式入門:捕獲組() vs 非捕獲組(?:) vs 命名捕獲組(?P<name>) - 儲蓄保險王

如果要把 http 和 https 分組,可以寫成:

pattern = r"(?:http|https)://\S+"

但更簡潔的寫法是:

pattern = r"https?://\S+"

這裡 s 的 ? 表示 s 可有可無。


13. 常見錯誤

錯誤一:不需要取值卻使用捕獲組

pattern = r"(cat|dog)\.txt"

如果你只想取得完整檔名,這會讓 findall() 只回傳:

['cat', 'dog']

應改成:

pattern = r"(?:cat|dog)\.txt"

結果:

['cat.txt', 'dog.txt']

錯誤二:以為圓括號只代表範圍

pattern = r"(cat|dog)\.txt"

圓括號不只是分組,它還會建立捕獲組。

如果不想建立捕獲組,請使用:

pattern = r"(?:cat|dog)\.txt"

錯誤三:捕獲組太多,導致 findall() 結果難以閱讀

不推薦:

pattern = r"(\w+)(\.)(txt|csv|json)"

如果只想取得完整檔名,可以改成:

pattern = r"\w+\.(?:txt|csv|json)"

如果只想取得檔名:

pattern = r"(\w+)\.(?:txt|csv|json)"

14. 完整比較表

Python 正規表示式入門:捕獲組() vs 非捕獲組(?:) vs 命名捕獲組(?P<name>) - 儲蓄保險王

15. 判斷口訣

可以用以下方式判斷:
我要取出這一段內容嗎?
如果答案是「要」:

(...)

如果答案是「不要,只是要把它們綁在一起」:

(?:...)

16. 最後範例

import re

text = "User: Alice, ID: 1001; User: Bob, ID: 1002"

pattern = r"User: (\w+), ID: (\d+)"

matches = re.findall(pattern, text)

print(matches)
Python 正規表示式入門:捕獲組() vs 非捕獲組(?:) vs 命名捕獲組(?P<name>) - 儲蓄保險王

這裡兩個捕獲組分別是:

(\w+)  → 使用者名稱
(\d+)  → 使用者 ID

如果只想找完整的 User 區塊,可以使用非捕獲組:

import re

text = "User: Alice, ID: 1001; User: Bob, ID: 1002"

pattern = r"(?:User: \w+, ID: \d+)"

matches = re.findall(pattern, text)

print(matches)
Python 正規表示式入門:捕獲組() vs 非捕獲組(?:) vs 命名捕獲組(?P<name>) - 儲蓄保險王

結論

捕獲組

(...)

用途是:

分組並保存匹配內容,方便之後用 group() 或 findall() 取出。

非捕獲組

(?:...)

用途是:

只分組與控制匹配範圍,不保存內容,
也不影響 findall() 的結果格式。

最重要的差異可以記成:

(...)     = 分組而且記住
(?:...)   = 分組但不記住

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

加入好友
加入社群
Python 正規表示式入門:捕獲組() vs 非捕獲組(?:) vs 命名捕獲組(?P<name>) - 儲蓄保險王

儲蓄保險王

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

You may also like...

發佈留言

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