re 模組

July 5, 2022

在 Python 中,使用 re 模組來支援規則表示式。

split/findall/sub

例如,若想切割字串,可以使用 re.split 函式:

>>> import re
>>> re.split(r'\d', 'Justin1Monica2Irene')
['Justin', 'Monica', 'Irene']
>>> re.split(r',', 'Justin,Monica,Irene')
['Justin', 'Monica', 'Irene']
>>> re.split(r'Orz', 'JustinOrzMonicaOrzIrene')
['Justin', 'Monica', 'Irene']
>>> re.split(r'\t', 'Justin\tMonica\tIrene')
['Justin', 'Monica', 'Irene']

re 模組的 split 函式,第一個參數要以字串來指定規則表示式,實際上若使用 Python 的字串表示時,因為 \ 等在Python字串中被作為轉義(Escape)字元,因此要撰寫規則表示式時,例如 \d,就必須撰寫為 '\\d',這樣當然很麻煩。

幸而 Python 中可以在字串前加上 r,表示這是個原始字串(Raw string),不要對 \ 做任何轉義動作,因此在撰寫規則表示式時,建議使用原始字串。

如果想找出字串中全部符合表示式的文字,可以使用 re.findall 函式,結果會以 list 傳回,若沒有符合就是傳回空 list

例如來實際看看貪婪量詞(Greedy quantifier)與逐步量詞(Reluctant quantifier)的差別:

>>> re.findall(r'.*foo', 'xfooxxxxxxfoo')
['xfooxxxxxxfoo']
>>> re.findall(r'.*?foo', 'xfooxxxxxxfoo')
['xfoo', 'xxxxxxfoo']

例如分別比對出來的名稱最後必須有或沒有 'Lin'

>>> re.findall(r'\w+\s(?=Lin)', 'Justin Lin, Monica Huang, Irene Lin')
['Justin ', 'Irene ']
>>> re.findall(r'\w+\s(?!Lin)', 'Justin Lin, Monica Huang, Irene Lin')
['Monica ']

如果要取代符合的子字串,可以使用 re.sub 函式。例如,將所有單引號都換成雙引號:

>>> re.sub("'", '"', "your right brain has nothing 'left' and your left brain has nothing 'right'")
'your right brain has nothing "left" and your left brain has nothing "right"'

如果規則表示式中有分組設定,在使用 sub 時,可以使用 \num 來捕捉被分組匹配的文字,num 表示第幾個分組。

例如,以下示範如何將使用者郵件位址從 .com 取代為 .cc:

>>> re.sub(r'(^[a-zA-Z]+\d*)@([a-z]+?.)com', r'\1@\2cc', 'caterpillar@openhome.com')
'caterpillar@openhome.cc'

整個規則表示式匹配了 'caterpillar@openhome.com',第一個分組捕捉到 'caterpillar',第二個分組捕捉到 'openhome.'\1\2 就分別代表這兩個部份。

如果使用了 (?P<name>…) 為分組命名,在呼叫 sub 函式時,必須使用 \g<name> 來參考。例如:

>>> re.sub(r'(?P<user>^[a-zA-Z]+\d*)@(?P<preCom>[a-z]+?.)com', r'\g<user>@\g<preCom>cc', 'caterpillar@openhome.com')
'caterpillar@openhome.cc'

subn/escape

re.sub 會將全部符合的文字都進行取代,傳回取代後的字串,re.subnre.sub 功能相同,然而傳回一個 Tuple,第一個元素是取代後的字串,第二個是符合的文字數量:

>>> re.sub(r'\d\d', 'Orz', '12dd1232')
'OrzddOrzOrz'
>>> re.subn(r'\d\d', 'Orz', '12dd1232')
('OrzddOrzOrz', 3)

在〈字元、字元類〉談過,詮譯字元在規則表示式中有特殊意義,例如 $ ^ * ( ) + = { } [ ] | \ : . ? 等,若要比對這些字元,則必須加上轉義(Escape)符號,即使 Python 有原始字串表示,自己處理這些事也還是麻煩,這時可以使用 re.escape 來代勞:

>>> re.escape(r'python.exe')
'python\\.exe'

search/match

如果想在搜尋某字串,看看是否有子字串符合規則表示式,可以使用 re.search,若無匹配傳回 None,若有匹配,會傳回 re.Match 物件,例如:

>>> import re
>>> m = re.search(r'\d{3}', "Kaohsiung 803, Road 12")
>>> m.group()
'803'
>>> m.start()
10
>>> m.end()
13

可以使用 Match 物件的 group 方法來取得符合整個規則表示式的子字串,使用 start 來取得子字串的起始索引,end 來取得結尾索引。

search 類似的是 match 函式,search 會在整個字串中,找尋第一個符合的子字串,而 match 只會在字串開頭看看接下來的字串是否符合,若有符合也是傳回 Match 物件,若無傳回 None

>>> m = re.match(r'\d{3}', "Kaohsiung 803, Road 12")
>>> m == None
True
>>> m = re.match(r'\d{3}', "803, Road 12")
>>> m.group()
'803'

若規則表示式有分組,Match 物件的 group 方法可以傳入數字,取得指定的分組,group 可以接受多個數字作為引數:

>>> m = re.match(r'(\w+) (\w+)', 'Isaac Newton, physicist')
>>> m.group(0)
'Isaac Newton'
>>> m.group(1)
'Isaac'
>>> m.group(2)
'Newton'
>>> m.group(1, 2)
('Isaac', 'Newton')

如果分組時有命名,group 也可以接受名稱:

>>> m = re.match(r'(?P<user>^[a-zA-Z]+\d*)@(?P<preCom>[a-z]+?.)com', 'caterpillar@openhome.com')
>>> m.group('user')
'caterpillar'
>>> m.group('preCom')
'openhome.'
>>> m['user']
'caterpillar'
>>> m[1]
'caterpillar'
>>> m.groups()
('caterpillar', 'openhome.')
>>> m.groupdict()
{'user': 'caterpillar', 'preCom': 'openhome.'}

由於 Match 實作了 __getitem__ 方法,如上所示,你也可以使用 [] 來取得分組結果;Match 物件的 groups 方法,則用來取得全部的分組結果,groupdict 用來取得 dict,包含了分組命名與結果。

更多 Match 物件的方法或特性,可以參考〈Match Objects〉。

如果要求整個字串符合規則表示式,可以使用 re.fullmatch(Python 3.4),例如:

>>> m = re.fullmatch(r'(?P<user>^[a-zA-Z]+\d*)@(?P<preCom>[a-z]+?.)com', 'caterpillar@openhome.com is valid')
>>> m == None
True
>>> re.match(r'(?P<user>^[a-zA-Z]+\d*)@(?P<preCom>[a-z]+?.)com', 'caterpillar@openhome.com is valid')
<re.Match object; span=(0, 24), match='caterpillar@openhome.com'>

分享到 LinkedIn 分享到 Facebook 分享到 Twitter