因為點號能匹配幾乎所有的字符,所以實際應用中許多人圖省事,隨意使用.*或.+,結果卻事與願違,下面以雙引號字符串為例來說明。
之前我們使用表達式"[^"]*"匹配雙引號字符串,而"圖省事"的做法是".*"。通常這么用是沒有問題的,但也可能有意外,例2-12就說明了一種如此。
例2-12 "圖省事"的意外結果
#字符串的值是"quoted string" print re.search(r"\".*\"", "\"quoted string\"").group(0) "quoted string" #字符串的值是"quoted string" and another" print re.search(r"\".*\"", "\"quoted string\" and another\"").group(0) "quoted string" and another" `
用".*"匹配雙引號字符串,不但可以匹配正常的雙引號字符串"quoted string",還可以匹配格式錯誤的字符串"quoted string" and another"。這是為什么呢?
這個問題比較復雜,現在只簡要介紹,以說明圖省事導致錯誤的原因,更深入的原因涉及正則表達式的匹配原理,在第8章詳細介紹。
在正則表達式".*"中,點號.可以匹配任何字符,*表示可以匹配的字符串長度沒有限制,所以.*在匹配過程結束以前,每遇到一個字符(除去無法匹配的\n),.*都可以匹配,但是到底是匹配這個字符,還是忽略它,將其交給之后的"來匹配呢?
答案是,具體選擇取決於所使用的量詞。在正則表達式中的量詞分為幾類,之前介紹的量詞都可以歸到一類,叫做匹配優先量詞(greedy quantifier,也有人翻譯為貪婪量詞 )。匹配優先量詞,顧名思義,就是在拿不准是否要匹配的時候,優先嘗試匹配,並且記下這個狀態,以備將來"反悔"。
來看表達式".*"對字符串"quoted string"的匹配過程。
一開始,"匹配",然后輪到字符q,.*可以匹配它,也可以不匹配,因為使用了匹配優先量詞,所以.*先匹配q,並且記錄下這個狀態【q也可能是.*不應該匹配的】;
接下來是字符u,.*可以匹配它,也可以不匹配,因為使用了匹配優先量詞,所以.*先匹配u,並且記錄下這個狀態【u也可能是.*不應該匹配的】;
……
現在輪到字符g,.*可以匹配它,也可以不匹配,因為使用了匹配優先量詞,所以.*先匹配g,並且記錄下這個狀態【g也可能是.*不應該匹配的】;
最后是末尾的",.*可以匹配它,也可以不匹配,因為使用了匹配優先量詞,所以.*先匹配",並且記錄下這個狀態【"也可能是.*不應該匹配的】。
這時候,字符串之后已經沒有字符了,但正則表達式中還有"沒有匹配,所以只能查詢之前保存備用的狀態,看看能不能退回幾步,照顧"的匹配。查詢到最近保存的狀態是:【"也可能是.*不應該匹配的】。於是讓.*"反悔"對"的匹配,把"交給",測試發現正好能匹配,所以整個匹配宣告成功。這個"反悔"的過程,專業術語叫做回溯(backtracking),具體的過程如圖2-1所示。
![]() |
圖2-1 表達式".*"對字符串"quoted string"的匹配過程 |
如果把字符串換成"quoted string" and another",.*會首先匹配第一個雙引號之后的所有字符,再進行回溯,表達式中的"匹配了字符串結尾的字符",整個匹配宣告完成,過程如圖2-2所示。
![]() |
圖2-2 表達式".*"的匹配過程 |
如果要准確匹配雙引號字符串,就不能圖省事使用".*",而要使用"[^"]*",過程如圖2-3所示。
![]() |
圖2-3 表達式"[^"]*"的匹配過程 |