Pattern 物件

July 5, 2022

剖析、驗證規則表示式往往是最耗時的階段,在頻繁使用某規則表示式的場合,若可以將剖析、驗證過後的規則表示式重複使用,對效率將會有幫助。

compile

re.compile 可以建立規則表示式物件,在剖析、驗證過規則表示式無誤後傳回的規則表示式物件可以重複使用。例如:

regex = re.compile(r'.*foo')

re.compile 函式可以指定 flags 參數,進一步設定規則表示式物件的行為,例如想不分大小寫比對 dog 文字,可以如下:

regex = re.compile(r'dog', re.IGNORECASE)

也可以在規則表示式中使用行內旗標(Inline flag)。例如 re.IGNORECASE 等效的嵌入旗標表示法為 (?i),以下片段效果等同上例:

regex= re.compile('(?i)dog')

行內旗標可使用的字元有 iamsLx,各自對應著 re.compile 函式的 flags 參數之作用:

  • re.IGNORECASEre.I(?i)
  • re.ASCIIre.A(?a)
  • re.MULTILINEre.M(?m)
  • re.DOTALLre.S(?s)
  • re.LOCALEre.L(?L)
  • re.VERBOSEre.X(?x)

Python 預設支援 Unicode 模式,若想令規則表示式的字元類回歸傳統僅比對ASCII的模式,可以使用 re.ASCIIre.MULTILINE 啟用多行文字模式(影響了 ^$ 的行為,換行字元後、前會被視為行首、行尾);預設情況下.不匹配換行字元,可設置 re.DOTALL 來匹配換行字元。

不建議設置 re.LOCALE,它會根據區域設定而影響 \w\W\b\B 以及大小寫判斷,因為區域機制並不可靠而且一次只能處理一種區域。

re.VERBOSE 可以「排版」規則表示式,空白(除非被放在字元類、反斜線後等情況,可參考 re 模組說明)會被忽略、可以使用 # 為規則表示式添加註解等。例如,底下的 ab 代表等效的規則表示式:

a = re.compile(r"""\d +  # 整數部份
                   \.    # 小數點
                   \d *  # 小數數字""", re.X)

b = re.compile(r"\d+\.\d*")

split/findall

在取得規則表示式物件後,可以使用 split 方法,將指定字串依規則表示式切割,效果等同於使用 re.split 函式;findall 方法找出符合的全部子字串,效果等同於使用 re.findall 函式:

>>> dog = re.compile('(?i)dog')
>>> dog.split('The Dog is mine and that dog is yours')
['The ', ' is mine and that ', ' is yours']
>>> dog.findall('The Dog is mine and that dog is yours')
['Dog', 'dog']
>>>

取得 Matcher

如果想取得符合時的更進一步資訊,可以使用 finditer 方法,它會傳回一個 iterable 物件,每一次迭代都會得到一個 Match 物件,可以使用它的 group 來取得符合整個規則表示式的子字串,使用 start 來取得子字串的起始索引,end 來取得結尾索引。例如:

>>> dog = re.compile('(?i)dog')
>>> for m in dog.finditer('The Dog is mine and that dog is yours'):
...     print(m.group(), 'between', m.start(), 'and', m.end())
...
Dog between 4 and 7
dog between 25 and 28
>>>

search 方法與 match 方法必須小心區分,search 會在整個字串中,找尋第一個符合的子字串,match 只在字串開頭看看接下來的字串是否符合,search 方法與 match 若有符合,都會傳回 Match 物件,否則傳回 None

>>> dog.search('The Dog is mine and that dog is yours')
<_sre.SRE_Match object; span=(4, 7), match='Dog'>
>>> dog.match('The Dog is mine and that dog is yours')
>>> dog.match('Dog is mine and that dog is yours')
<_sre.SRE_Match object; span=(0, 3), match='Dog'>
>>>

分組處理

如果規則表示式中設定了分組,findall 方法會以清單傳回各個分組。例如 (\d\d)\1 的話,表示要輸入四個數字,輸入的前兩個數字與後兩個數字必須相同:

>>> twins = re.compile(r'(\d\d)\1')
>>> twins.findall('12341212345453999928202')
['12', '45', '99']
>>>

能符合的數字只有 1212、4545、9999,因為分組設定是 (\d\d) 兩個數字,而 findall 以清單傳回各個分組,因此結果是 12、45、99,如果想取得 1212、4545、9999 這樣的結果,方式之一是再設一層分組,取外層分組結果:

>>> twins = re.compile(r'((\d\d)\2)')
>>> twins.findall('12341212345453999928202')
[('1212', '12'), ('4545', '45'), ('9999', '99')]
>>>

另一個方式是使用 finditer 方法,透過迭代 Match 並呼叫 group,這會取得符合整個規則表示式的子字串。例如:

>>> for m in twins.finditer('12341212345453999928202'):
...     print(m.group())
...
1212
4545
9999
>>>

先前談到,(["'])[^"']*\1 可比對出前後引號必須一致的狀況,若想找出單引號或雙引號中的文字,如下使用 findall 是行不通的:

>>> regex = re.compile(r'''(["'])[^"']*\1''')
>>> regex.findall(r'''your right brain has nothing 'left' and your left has nothing "right"''')
["'", '"']
>>>

因為 findall 以清單傳回各個分組,而分組設定為 (["']),符合的是單引號或雙引號,因此清單中才會只看到’與",如果要找出單引號或雙引號中的文字,必須如下:

>>> import re
>>> regex = re.compile(r'''(["'])[^"']*\1''')
>>> for m in regex.finditer(r'''your right brain has nothing 'left' and your left has nothing "right"'''):
...     print(m.group())
...
'left'
"right"
>>>

如果設定了分組,searchmatch 在搜尋到文字時,也可以使用 group 指定數字,表示要取得哪個分組,或者是使用 groups 傳回一個 tuple,其中包含符合的分組。例如:

>>> regex = re.compile(r'''(["'])([^"']*)\1''')
>>> m = regex.search(r"your right brain has nothing 'left'")
>>> m.group(1)
"'"
>>> m.group(2)
'left'
>>> m.groups()
("'", 'left')
>>> m.group(0)
"'left'"
>>>

group(0) 實際上等於呼叫 group() 不指定數字,表示整個符合規則表示式的字串。如果使用了 (?P<name>…) 為分組命名,在呼叫 group() 方法時,也可以指定分組名稱。例如:

>>> twins = re.compile(r'(?P<tens>\d\d)(?P=tens)')
>>> m = twins.search('12341212345453999928202')
>>> m.group('tens')
'12'
>>>

字串取代

如果要取代符合的子字串,可以使用規則表示式物件的sub()方法。例如,將單引號都換成雙引號:

>>> regex = re.compile(r"'")
>>> regex.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:

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

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

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

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

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