在 Python 正規表示式中,圓括號 () 有兩種常見用途:
- 捕獲組 Capturing Group
- 非捕獲組 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)輸出:
這個例子中,兩者結果看起來一樣,因為整個分組內容就是完整匹配內容。
差異要在搭配其他內容時才會明顯。
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)輸出:
為什麼結果不同?
捕獲組 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)輸出:
有一個捕獲組
import re
text = "cat.txt dog.txt"
result = re.findall(r"(cat|dog)\.txt", text)
print(result)輸出:
有兩個捕獲組
import re
text = "cat.txt dog.txt"
result = re.findall(r"(cat|dog)(\.txt)", text)
print(result)因為有兩個捕獲組:
(cat|dog) → 第 1 組
(\.txt) → 第 2 組所以每個匹配結果會變成 tuple。
規則整理
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())輸出:
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)這裡:
(?: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)因為:
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))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)這裡:
(?: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)這裡:
(\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())命名捕獲組比數字編號更容易閱讀:
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))12. 什麼時候使用非捕獲組?
如果你只需要完整匹配,不需要中間結果,就使用非捕獲組。
例如:
import re
text = "https://example.com http://example.org"
pattern = r"https?://\S+"
result = re.findall(pattern, text)
print(result)如果要把 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. 完整比較表
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)這裡兩個捕獲組分別是:
(\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)結論
捕獲組
(...)用途是:
分組並保存匹配內容,方便之後用
group()或findall()取出。
非捕獲組
(?:...)用途是:
只分組與控制匹配範圍,不保存內容,
也不影響findall()的結果格式。
最重要的差異可以記成:
(...) = 分組,而且記住
(?:...) = 分組,但不記住推薦hahow線上學習python: https://igrape.net/30afN