一、背景說明
自從整理了“正則表達式書寫規則說明”后,使用正則表達式的地方都基本能應對。唯一搞不清的是不懂為什么re.search的還要用group()才能獲取匹配的結果(而且是group這么個感覺和獲取字符串完全不搭邊的名字),正是沒搞清的這點留下了很大的隱患。
上周同事問正則中重復次數只能作用於其前邊的那一個字符,如何能讓重復次數能作用於其前邊的多個字符,自己信心滿滿地說加括號就完事了,比如ab*就寫成(ab)*,但同事說不行。回頭進行驗證發現這種寫法在findall中確實有問題。
二、問題示例
需求:從一段文字中提取出所有版本號。
測試代碼如下:
import re # 包含版本號的一段文字 text = "1.2.3 and 1.2.4" # 設想中的正則寫法。一個數字開頭,后邊的.加數值重復一次或多次 regex = "\d(\.\d)+" # 輸出結果為'1.2.3',與預期結果一致 print(re.search(regex, text).group()) # 預期結果為['1.2.3', '1.2.4'] # 實際結果為['.3', '.4'] print(re.findall(regex, text))
運行結果如下,search輸出結果符合預期,但findall輸出結果為['.3', '.4']和預想的['1.2.3', '1.2.4']完全不一樣:
三、處理辦法
多番google后找到這篇文章,說可以用?:進行處理,使用?:去掉其外一層括號的標識作用只保留其分組作用即可解決該問題。
代碼修改如下:

import re # 包含版本號的一段文字 text = "1.2.3 and 1.2.4" # 設想中的正則寫法。一個數字開頭,后邊的.加數值重復一次或多次 regex = "\d(?:\.\d)+" # 輸出結果為'1.2.3',與預期結果一致 print(re.search(regex, text).group()) # 輸出結果為['1.2.3', '1.2.4'],與預期一致 print(re.findall(regex, text))
運行結果如下,確實能成功提取出版本號:
四、處理原理
雖說使用(?:...)的寫法解決了re.findall未能提取版本號的問題,但一是搞不清所謂的“標識”、“分組”是個什么概念,二是同樣的正則在search和findall中表現差異讓人費解。我們再來分析一下。
4.1 (...)的本質
按文檔理解是,小括號的本質是標識一個分組,有多少個小括號就有多少個分組,search和findall都會針對每個分組進行匹配但處理略有差別:
search任何時候都將整個正則視為一個分組,group(0),這也是search的默認返回結果,其他括號也都算一個分組可通過group(index)方法來獲取對應分組的匹配結果;
findall在沒有括號時將整個正則視為一個分組,有括號時只認括號內的分組,將所有分組匹配結果以元組列表形式返回。
但實際測試后在匹配細節上還是不好理解,但同時沒現在還沒感覺到這種復雜的分組有什么很大的作用,這暫不深究。
4.2 (?:...)的作用
通過上邊的分析我們可以看到,search和findall的區別是search無論何時都將整個正則視為分組,而findall只有在沒有括號時才將整個正則視為分組。
而(?:...)的作用就是降低括號的分組功能,使其只在語法分析時視為一個整體但在進行匹配時並不認為是一個分組,這樣findall就會像沒有括號時那樣將整個正則視為分組。此亦即所謂的非捕獲(non-capturing)分組。
參考:
https://www.stat.berkeley.edu/~spector/extension/python/notes/node84.html