Python3 如何優雅地使用正規表示式(詳解二)

2022-07-10 02:42:10 字數 3893 閱讀 7159

使用正規表示式

現在我們開始來寫一些簡單的正規表示式吧。python 通過 re 模組為正規表示式引擎提供乙個介面,同時允許你將正規表示式編譯成模式物件,並用它們來進行匹配。

小甲魚解釋:re 模組是使用 c 語言編寫,所以效率比你用普通的字串方法要高得多;將正規表示式進行編譯(compile)也是為了進一步提高效率;後邊我們會經常提到「模式」,指的就是正規表示式被編譯成的模式物件。

編譯正規表示式

正規表示式被編譯為模式物件,該物件擁有各種方法供你操作字串,如查詢模式匹配或者執行字串替換。

>>> import re

>>> p = re.compile('ab*')

>>> p  

<_sre.sre_pattern object at 0x...>

複製**

re.compile() 也可以接受 flags 引數,用於開啟各種特殊功能和語法變化,我們會在後邊一一介紹。

現在我們先來看個簡單的例子:

>>> p = re.compile('ab*', re.ignorecase)

複製**

正規表示式作為乙個字串引數傳給 re.compile()。由於正規表示式並不是 python 的核心部分,因此沒有為它提供特殊的語法支援,所以正規表示式只能以字串的形式表示。(有些應用根本就不需要使用到正規表示式,所以 python 社群的小夥伴們認為沒有必要將其納入 python 的核心。)相反,re 模組僅僅是作為 c 的擴充套件模組包含在 python 中,就像 socket 模組和 zlib 模組。

使用字串來表示正規表示式保持了 python 簡潔的一貫風格,但也因此有一些負面影響,下邊我們就來談一談。

麻煩的反斜槓

現在的情況是,你需要在 latex 檔案中使用正規表示式匹配字串 '\section'。因為反斜槓作為需要匹配的特殊字元,所以你需要再它前邊加多乙個反斜槓來剝奪它的特殊功能。所以我們會把正規表示式的字元寫成 '\\section'。

但不要忘了,python 在字串中同樣使用反斜槓來表示特殊意義。因此,如果我們想將 '\\section' 完整地傳給 re.compile(),我們需要再次新增兩個反斜槓......

匹配字元

匹配階段

\section

需要匹配的字串

\\section

正規表示式使用 '\\' 表示匹配字元 '\'

"\\\\section"

不巧,python 字串也使用 '\\' 表示字元 '\'

簡而言之,為了匹配反斜槓這個字元,我們需要在字串中使用四個反斜槓才行。所以,在正規表示式中頻繁地使用反斜槓,會造成反斜槓風暴,進而導致你的字串極其難懂。

解決方法是使用 python 的原始字串來表示正規表示式(就是在字串前邊加上 r,大家還記得吧...):

正則字串

原始字串

"ab*"

r"ab*"

"\\\\section"

r"\\section"

"\\w+\\s+\\1"

r"\w+\s+\1"

小甲魚解釋:強烈建議使用原始字串來表達正規表示式。

實現匹配

當你將正規表示式編譯之後,你就得到乙個模式物件。那你拿他可以用來做什麼呢?模式物件擁有很多方法和屬性,我們下邊列舉最重要的幾個來講:

方法功能

match()

判斷乙個正規表示式是否從開始處匹配乙個字串

search()

遍歷字串,找到正規表示式匹配的第乙個位置

findall()

遍歷字串,找到正規表示式匹配的所有位置,並以列表的形式返回

finditer()

遍歷字串,找到正規表示式匹配的所有位置,並以迭代器的形式返回

如果沒有找到任何匹配的話,match() 和 search() 會返回 none;如果匹配成功,則會返回乙個匹配物件(match object),包含所有匹配的資訊:例如從哪兒開始,到哪兒結束,匹配的子字串等等。

接下來我們一步步講解:

>>> import re

>>> p = re.compile('[a-z]+')

>>> p

re.compile('[a-z]+')

複製**

現在,你可以嘗試使用正規表示式 [a-z]+ 去匹配各種字串。

例如:>>> p.match("")

>>> print(p.match(""))

none

複製**

因為 + 表示匹配一次或者多次,所以空字串不能被匹配。因此,match() 返回 none。

我們再嘗試乙個可以匹配的字串:

>>> m = p.match('fishc')

>>> m  

<_sre.sre_match object; span=(0, 5), match='fishc'>

複製**

在這個例子中,match() 返回乙個匹配物件,我們將其存放在變數 m 中,以便日後使用。

接下來讓我們來看看匹配物件裡邊有哪些資訊吧。匹配物件包含了很多方法和屬性,以下幾個是最重要的:

方法功能

group()

返回匹配的字串

start()

返回匹配的開始位置

end()

返回匹配的結束位置

span()

返回乙個元組表示匹配位置(開始,結束)

>>> m.group()

'fishc'

>>> m.start()

0>>> m.end()

5>>> m.span()

(0, 5)

複製**

由於 match() 只檢查正規表示式是否在字串的起始位置匹配,所以 start() 總是返回 0。

然而,search() 方法可就不一樣咯:

>>> print(p.match('^_^fishc'))

none

>>> m = p.search('^_^fishc')

>>> print(m)

<_sre.sre_match object; span=(3, 8), match='fishc'>

>>> m.group()

'fishc'

>>> m.span()

(3, 8)

複製**

在實際應用中,最常用的方式是將匹配物件存放在乙個區域性變數中,並檢查其返回值是否為 none。

形式通常如下:

p = re.compile( ... )

m = p.match( 'string goes here' )

if m:

print('match found: ', m.group())

else:

print('no match')

複製**

有兩個方法可以返回所有的匹配結果,乙個是 findall(),另乙個是 finditer()。

findall() 返回的是乙個列表:

>>> p = re.compile('\d+')

>>> p.findall('3只小甲魚,15條腿,多出的3條在**?')

['3', '15', '3']

複製**

findall() 需要在返回前先建立乙個列表,而 finditer() 則是將匹配物件作為乙個迭代器返回:

>>> iterator = p.finditer('3只小甲魚,15條腿,還有3條去了**?')

>>> iterator

>>> for match in iterator:

print(match.span())

(0, 1)

(6, 8)

(13, 14)

複製**

小甲魚解釋:如果列表很大,那麼返回迭代器的效率要高很多。迭代器的相關知識請看:《零基礎入門學習python》048 | 魔法方法:迭代器

Python3 如何優雅地使用正規表示式(詳解五)

非捕獲組命名組 精心設計的正規表示式可能會劃分很多組,這些組不僅可以匹配相關的子串,還能夠對正規表示式本身進行分組和結構化。在複雜的正規表示式中,由於有太多的組,因此通過組的序號來跟蹤和使用會變得困難。有兩個新的功能可以幫你解決這個問題 非捕獲組和命名組 它們都使用了乙個公共的正規表示式擴充套件語法...

Python3 如何優雅地使用正規表示式(詳解六)

修改字串 我們已經介紹完如何對字元進行搜尋,接下來我們講講正規表示式如何修改字串。正規表示式使用以下方法修改字串 方法用途 split 在正規表示式匹配的地方進行分割,並返回乙個列表 sub 找到所有匹配的子字串,並替換為新的內容 subn 跟 sub 幹一樣的勾當,但返回新的字串以及替換的數目 分...

Python3 如何優雅地使用正規表示式(詳解二)

現在我們開始來寫一些簡單的正規表示式吧。python 通過 re 模組為正規表示式引擎提供乙個介面,同時允許你將正規表示式編譯成模式物件,並用它們來進行匹配。解釋 re 模組是使用 c 語言編寫,所以效率比你用普通的字串方法要高得多 將正規表示式進行編譯 compile 也是為了進一步提高效率 後邊...