Python3 如何優雅地使用正則表示式(詳解七)
常見問題
正則表示式是一個非常強大的工具,但在有些時候它並不能直觀地按照你的意願來執行。本篇我們將指出一些最常見的錯誤。
使用字串方法
有時使用 re 模組是個錯誤!如果你匹配一個固定的字串或者單個字元類,並且你沒有使用 re 的任何標誌(像 IGNORECASE 標誌),那麼就沒有必要使用正則表示式了。字串有一些方法是對固定字串進行操作的,並且它們通常比較快。因為它們都是獨立優化的 C 語言小迴圈,目的是在簡單的情況下代替功能更加強大、更具通用性的正則表示式引擎。
舉個例子,例如你想把字串中所有的
另一個常見的情況是從一個字串中刪除單個字元或者用另一個字元替代它。你也許會想到用 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 = '<html><head><title>Title</title>'
>>> len(s)
32
>>> print(re.match('<.*>', s).span())
(0, 32)
>>> print(re.match('<.*>', s).group())
<html><head><title>Title</title>
RE 匹配在 <html> 的 < 後,.* 消耗掉字串的剩餘部分。由於正則表示式預設是貪婪的原因,RE 必須從字串的尾部一個字元一個字元地回溯,直到找到匹配的 >。大家看到,按照這種方法,最後找到匹配內容竟是 <html> 的 < 開始,到 </title> 的 > 結束。顯然這不是你想要的結果。
在這種情況下,解決方案是使用非貪婪的限定符 *?、+?、?? 或 {m,n}?,儘可能地匹配小的文字。
>>> print(re.match('<.*?>', s).group())
<html>
在上邊的例子中,> 在第一個 < 被匹配後立刻嘗試匹配,如果失敗,匹配引擎前進一步,嘗試下一個字元,直到第一次匹配 >,這樣就得到了我們想要的結果。
注意,使用正則表示式分析 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>[^:]+) # Header name
\s* : # Whitespace, and a colon
(?P<value>.*?) # 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<header>[^:]+)\s*:(?P<value>.*?)\s*$")