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

2022-07-10 02:39:11 字數 3115 閱讀 8971

常見問題

正規表示式是乙個非常強大的工具,但在有些時候它並不能直觀地按照你的意願來執行。本篇我們將指出一些最常見的錯誤。

使用字串方法

有時使用 re 模組是個錯誤!如果你匹配乙個固定的字串或者單個字元類,並且你沒有使用 re 的任何標誌(像 ignorecase 標誌),那麼就沒有必要使用正規表示式了。字串有一些方法是對固定字串進行操作的,並且它們通常比較快。因為它們都是獨立優化的 c 語言小迴圈,目的是在簡單的情況下代替功能更加強大、更具通用性的正規表示式引擎。

舉個例子,例如你想把字串中所有的 dead 替換成 word,你會想到使用正規表示式的 re.sub() 方法來實現,但這麼簡單的替換,還是考慮直接使用字串的 replace() 方法吧。但有一點你需要注意,就是 replace() 會在單詞裡邊進行替換,像swordfish 會變成 sdeedfish,這顯然不是你想要的!replace() 沒辦法識別單詞的邊界,因此你才來考慮使用正規表示式。只需要將 re 的模式寫成 \bword\b 即可勝任此任務。

另乙個常見的情況是從乙個字串中刪除單個字元或者用另乙個字元替代它。你也許會想到用 re.sub('\n', ' ', s) 這樣的正規表示式來實現,但其實字元的 translate() 方法完全能夠勝任這個任務,並且比任何正規表示式操作起來更快些。

簡而言之,在使用 re 模組之前,先考慮一下你的問題是否可以用更快速、簡單的字串自帶方法來解決。

match() vs search()

match() 函式只會檢查 re 是否在字串的開始處匹配,而 search() 會遍歷整個字串搜尋匹配的內容。記住這一區別很重要。再次強調一下,match() 只會報告一次成功的匹配,並且匹配的位置必須是從字串的第乙個字元開始:

>>> print(re.match('super', 'superstition').span())

(0, 5)

>>> print(re.match('super', 'insuperable'))

none

複製**

另一方面,search() 函式將遍歷整個字串,並報告它找到的第乙個匹配:

>>> print(re.search('super', 'superstition').span())

(0, 5)

>>> print(re.search('super', 'insuperable').span())

(2, 7)

複製**

有時候你可能會耍點小聰明,使用 re.match() 然後在 re 的前邊加上 .*。但盡量不要這麼做,最好採用 re.search() 代替。正規表示式編譯器會對 res 做一些分析,以便可以在搜尋匹配時提高速度。一般分析會先找到匹配的第乙個字元是什麼。舉個例子,模式 crow 必須從字元 'c' 開始匹配,那麼匹配引擎分析後會快速遍歷字串,然後在 'c' 被找到之後才開始全部匹配。

按照上面的分析,你新增乙個 .* 會導致這個優化失敗,這就需要從頭到尾掃瞄一遍,然後再回溯匹配 re 剩餘的部分。所以,請使用 re.search() 代替。

貪婪 vs 非貪婪

當重複乙個正規表示式時,如果使用 a*,那麼結果是盡可能多地去匹配。當你嘗試匹配一對對稱的定界符,例如 html 標誌中的尖括號,預設的貪婪模式會使得你很困擾。

我們來看下例子:

>>> s = 'title'

>>> len(s)

32>>> print(re.match('<.*>', s).span())

(0, 32)

>>> print(re.match('<.*>', s).group())

title

複製**

re 匹配在  的 < 後,.* 消耗掉字串的剩餘部分。由於正規表示式預設是貪婪的原因,re 必須從字串的尾部乙個字元乙個字元地回溯,直到找到匹配的 >。大家看到,按照這種方法,最後找到匹配內容竟是  的 < 開始,到

的 > 結束。顯然這不是你想要的結果。

在這種情況下,解決方案是使用非貪婪的限定符 *?、+?、?? 或 ?,盡可能地匹配小的文字。

>>> print(re.match('<.*?>', s).group())

複製**

在上邊的例子中,> 在第乙個 < 被匹配後立刻嘗試匹配,如果失敗,匹配引擎前進一步,嘗試下乙個字元,直到第一次匹配 >,這樣就得到了我們想要的結果。

注意,使用正規表示式分析 html 和 xml 是很痛苦的。當你編寫乙個正規表示式去處理所有可能的情況時,你會發現 html 和 xml 總會打破你的「規則」,這讓你很頭疼......像這樣的話,建議使用 html 和 xml 解析器來處理更合適。

使用 re.verbose

現在你應該意識到了,正規表示式的表示非常緊湊。這也帶來了乙個問題,就是不好閱讀。中等複雜的正規表示式可能包含許多反斜槓、圓括號和元字元,以至於難以讀懂。

在這些 res 中,當編譯正規表示式時指定 re.verbose 標誌是非常有幫助的。因為它允許你可以編輯正規表示式的格式,使之更清楚。

re.verbose 標誌有幾個作用。在正規表示式中不在字元類中的空白字元將被忽略。這就意味著像 i love fishc 這樣的表示式和可讀性較差的 ilovefishc 相同。但 [a b] 將匹配字元 'a'、'b' 或 ' ';另外,你也可以把注釋放到 re 中,注釋是從 # 開始到下一行。當使用三引號字串時,會使得 res 的格式更整潔:

pat = re.compile(r"""

\s*                             # skip leading whitespace

(?p[^:]+)   # header name

\s* :                           # whitespace, and a colon

(?p.*?)          # the header's value -- *? used to

# lose the following trailing whitespace

\s*$                           # trailing whitespace to end-of-line

""", re.verbose)

同樣的內容,下邊這個要難讀得多:

pat = re.compile(r"\s*(?p[^:]+)\s*:(?p.*?)\s*$")

《完》

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

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

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

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

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

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