re.findall不匹配子刮號()的文字

意外之外的匹配結果…?

re.findall會將一段字串中匹配到的所有文字回傳成一組list

但在Python中,若正則內有小刮號匹配的話,會發現回傳資料變成由Tuple組成的list,即小刮號裡的文字也會被匹配出來!

直接看例子:

1
2
3
4
5
pattern = r'(A(B)+C)' # 為了明顯表達差異,最外層故意多了一層()
useFindall = re.findall( pattern, 'ABC, ABBC, AB, BB' )
print( '使用findall:', useFindall )
useMatch = re.match( pattern, 'ABC, ABBC, AB, BB' )
print( '使用match:', useMatch.groups() )

我們期望這段正則表達式匹配到的結果是['ABC'、'ABBC']

也就是組出所有中間有1個以上的B,而頭尾被AC包住的的文字

若像我一樣會習慣會在RegExr或其他線上網站先寫好再貼上的話,就會發現Python得到的結果完全不如預期

上段程式碼執行結果:

1
2
使用findall: [('ABC', 'B'), ('ABBC', 'B')]
使用match: ('ABC', 'B')

發現他居然將內部(B)裡面再獨立匹配並回傳出來!

這還是有在最外層多包()才會發現的。一般書寫情況通常沒事不會在最外層包()
將上述範例的pattern最外層刮號拿掉(即pattern = r'A(B)+C')

匹配出來的結果完全會是預料之外!僅會得到最裡面(B)的結果!

1
2
使用findall: ['B', 'B']
使用match: ('B',)

當表達式寫得稍微複雜時,完全不會想到他回傳的是最裡面()的結果,只會覺得是自己的正則表達寫錯了……
然後就會陷入無盡的try and error,任時光匆匆流去…

實際上這樣的輸出流程是:

  1. 先匹配出有符合頭尾AC包住,中間N個B的結果
  2. 再匹配子刮號裡的結果,即B。若裡面還有子刮號,則繼續往內部匹配
  3. 組成list,回傳最內部的結果

解決方法就是關閉他的內部匹配

在所有子刮號裡面加上前綴?:

pattern = r'A(?:B)+C'

再執行上述範例,findall就會得到預期中的結果:

1
2
使用findall: ['ABC', 'ABBC']
使用match: ()

但會發現:咦,怎麼re.match就沒東西了?因為是使用.groups()的關係

將re.match的.groups()拿掉,會發現match其實也有正確匹配到預期的結果

使用match: <_sre.SRE_Match object; span=(0, 3), match='ABC'>

或著將pattern = r'(A(?:B)+C)'也會讓match順利輸出使用match: ('ABC',)
不過這是題外話了

這篇主要讓re.findall順利得到期望的結果。大部份使用情況應該也是findall居多吧 XD

參考資料

Stack Overflow / re.findall not returning full match?