1. 程式人生 > >Python3 如何優雅地使用正則表示式(詳解一)

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

正則表示式介紹

正則表示式(Regular expressions 也稱為 REs,或 regexes 或 regex patterns)本質上是一個微小的且高度專業化的程式語言。它被嵌入到 Python 中,並通過 re 模組提供給程式猿使用。使用正則表示式,你需要指定一些規則來描述那些你希望匹配的字串集合。這些字串集合可能包含英語句子、 e-mail 地址、TeX 命令,或任何你想要的東東。

正則表示式模式被編譯成一系列的位元組碼,然後由一個 C 語言寫的匹配引擎所執行。對於高階的使用,你可能需要更關注匹配引擎是如何執行給定的 RE,並通過一定的方式來編寫 RE,以便產生一個可以執行得更快的位元組碼。本文暫不講解優化的細節,因為這需要你對匹配引擎的內部機制有一個很好的理解。但本文的例子均是符合標準的正則表示式語法。



註釋:Python 的正則表示式引擎是用 C 語言寫的,所以效率是極高的。另,所謂的正則表示式,這裡說的 RE,就是上文我們提到的“一些規則”。

正則表示式語言相對較小,並且受到限制,所以不是所有可能的字串處理任務都可以使用正則表示式來完成。還有一些特殊的任務,可以使用正則表示式來完成,但是表示式會因此而變得非常複雜。在這種情況下,你可能通過自己編寫 Python 程式碼來處理會更好些;儘管 Python 程式碼比一個精巧的正則表示式執行起來會慢一些,但可能會更容易理解。

註釋:這可能是大家常說的“醜話說在前”吧,大家別管他,正則表示式非常優秀,她可以處理你 98.3% 的文字任務,一定要好好學哦~~~~~

 

簡單的模式

我們將從最簡單的正則表示式學習開始。由於正則表示式常用於操作字串的,因此我們從最常見的任務下手:字元匹配。

 

字元匹配

大多數字母和字元會匹配它們自身。舉個例子,正則表示式 FishC 將完全匹配字串 "FishC"。(你可以啟用不區分大小寫模式,這將使得 FishC 可以匹配 "FISHC" 或 "fishc",我們會在後邊討論這個話題。)

當然這個規則也有例外。有少數特殊的字元我們稱之為元字元(metacharacter),它們並不能匹配自身,它們定義了字元類、子組匹配和模式重複次數等。本文用很大的篇幅專門討論了各種元字元及其作用。



下邊是元字元的完整列表(我們將在後邊逐一講解):

.   ^   $   *   +   ?   { }   [ ]   \   |   ( )

註釋:如果沒有這些元字元,正則表示式就變得跟字串的 find() 方法一樣平庸了......


我們先來看下方括號 [ ],它們指定一個字元類用於存放你需要匹配的字元集合。可以單獨列出需要匹配的字元,也可以通過兩個字元和一個橫杆 指定匹配的範圍。例如 [abc] 會匹配字元 a,b 或 c[a-c] 可以實現相同的功能。後者使用範圍來表示與前者相同的字元集合。如果你想只匹配小寫字母,你的 RE 可以寫成 [a-z]

需要注意的一點是:元字元在方括號中不會觸發“特殊功能”,在字元類中,它們只匹配自身。例如 [akm$] 會匹配任何字元 'a','k','m' 或 '$''$' 是一個元字元,但在方括號中它不表示特殊含義,它只匹配 '$'字元本身。

你還可以匹配方括號中未列出的所有其他字元。做法是在類的開頭新增一個脫字元號 ^ ,例如 [^5] 會匹配除了 '5' 之外的任何字元。


或許最重要的元字元當屬反斜槓 了。跟 Python 的字串規則一樣,如果在反斜槓後邊緊跟著一個元字元,那麼元字元的“特殊功能”也不會被觸發。例如你需要匹配符號 [  \,你可以在它們前面加上一個反斜槓,以消除它們的特殊功能:\[,\\

反斜槓後邊跟一些字元還可以表示特殊的意義,例如表示十進位制數字,表示所有的字母或者表示非空白的字元集合。

解釋:反斜槓真牛逼,反斜槓後邊跟元字元去除特殊功能,反斜槓後邊跟普通字元實現特殊功能。

讓我們來舉個例子:\w 匹配任何單詞字元。如果正則表示式以位元組的形式表示,這相當於字元類 [a-zA-Z0-9_];如果正則表示式是一個字串,\w 會匹配所有 Unicode 資料庫(unicodedata 模組提供)中標記為字母的字元。你可以在編譯正則表示式的時候,通過提供 re.ASCII 表示進一步限制 \w 的定義。

解釋:re.ASCII 標誌使得 \w 只能匹配 ASCII 字元,不要忘了,Python3 是 Unicode 的。

下邊列舉一些反斜槓加字元構成的特殊含義:
 

特殊字元

含義

\d

匹配任何十進位制數字;相當於類 [0-9]

\D

 \d 相反,匹配任何非十進位制數字的字元;相當於類 [^0-9]

\s

匹配任何空白字元(包含空格、換行符、製表符等);相當於類 [ \t\n\r\f\v]

\S

 \s 相反,匹配任何非空白字元;相當於類 [^ \t\n\r\f\v]

\w

匹配任何單詞字元,見上方解釋

\W

\w 相反

\b

匹配單詞的開始或結束

\B

與 \b 相反


它們可以包含在一個字元類中,並且一樣擁有特殊含義。例如 [\s,.] 是一個字元類,它將匹配任何空白字元(/s 的特殊含義),',' 或 '.'。 

最後我們要講的一個元字元是 .,它匹配除了換行符以外的任何字元。如果設定了 re.DOTALL 標誌,將匹配包括換行符在內的任何字元。

 

重複的事情

使用正則表示式能夠輕鬆的匹配不同的字元集合,但 Python 字串現有的方法卻無法實現。然而,如果你認為這是正則表示式的唯一優勢,那你就 too young too native 了。正則表示式有另一個強大的功能,就是你可以指定 RE 部分被重複的次數。


我們來看看 * 這個元字元,當然它不是匹配 '*' 字元本身(我們說過元字元都是有特殊能力的),它用於指定前一個字元匹配零次或者多次。

例如 ca*t 將匹配 ct(0 個字元 a),cat(1 個字元 a),caaat(3 個字元 a),等等。需要注意的是,由於受到 C 語言的 int 型別大小的內部限制,正則表示式引擎會限制字元 'a' 的重複個數不超過 20 億個;不過,通常我們工作中也用不到那麼大的資料。


正則表示式預設的重複規則是貪婪的,當你重複匹配一個 RE 時,匹配引擎會嘗試儘可能多的去匹配。直到 RE 不匹配或者到了結尾,匹配引擎就會回退一個字元,然後再繼續嘗試匹配。

我們通過例子一步步的給大家講解什麼叫“貪婪”:先考慮一下表達式 a[bcd]*b,首先需要匹配字元 'a',然後是零個到多個 [bcd],最後以 'b' 結尾。那現在想象一下,這個 RE 匹配字串 abcbd 會怎樣?
 

步驟 匹配 說明
1 a 匹配 RE 的第一個字元 'a'
2

abcbd

引擎在符合規則的情況下儘可能地匹配 [bcd]*,直到該字串的結尾
3 失敗 引擎嘗試匹配 RE 最後一個字元 'b',但當前位置已經是字串的結尾,所以失敗告終
4 abcb 回退,所以 [bcd]* 匹配少一個字元
5 失敗 再一次嘗試匹配 RE 最後一個字元 'b',但字串最後一個字元是 'd',所以失敗告終
6 abc 再次回退,所以 [bcd]* 這次只匹配 'bc'
7 abcb 再一次嘗試匹配字元 'b',這一次字串當前位置指向的字元正好是 'b',匹配成功


最終,RE 匹配的結果是 abcb

解釋:正則表示式預設的匹配規則是貪婪的,後邊有教你如何使用非貪婪的方法匹配。


另一個實現重複的元字元是 +,用於指定前一個字元匹配一次或者多次。

要特別注意 * 和 + 的區別:匹配的是零次或者多次,所以被重複的內容可能壓根兒不會出現;至少需要出現一次。例如 ca+t 會匹配 cat 和 caaat,但不會匹配 ct


還有兩個表示重複的元字元,其中一個是問號 ?,用於指定前一個字元匹配零次或者一次。你可以這麼想,它的作用就是把某種東西標誌位可選的。例如 ?甲魚 可以匹配 小甲魚,也可以匹配 甲魚


最靈活的應該是元字元 {m,n}(m 和 n 都是十進位制整數),上邊講到的幾個元字元都可以使用它來表達,它的含義是前一個字元必須匹配 m 次到 n 次之間。例如 a/{1,3}b 會匹配 a/ba//b 和 a///b。但不會匹配 ab(沒有斜槓);也不會匹配 a////b(斜槓超過三個)。

你可以省略 m 或者 n,這樣的話,引擎會假定一個合理的值代替。省略 m,將被解釋為下限 0;省略 n 則會被解釋為無窮大(事實上是上邊我們提到的 20 億)。

解釋:如果是 {,n} 相當於 {0,n};如果是 {m,} 相當於 {m,+無窮};如果是 {n},則是重複前一個字元 n 次。另外還有一個超容易出錯的是寫成 {m, n},看著挺美,但注意,正則表示式裡邊不能隨意新增空格,不然會改變原來的含義。


聰明的魚油應該已經發現了,其實 *、+ 和 ? 都可以使用 {m,n} 來代替{0,} 跟 * 是一樣的{1,} 跟 + 是一樣的{0,1} 跟 ? 是一樣的。不過還是鼓勵大家記住並使用 *、+ 和 ?,因為這些字元更短並且更容易閱讀。

解釋:還有一個原因是匹配引擎對 * + ? 做了優化,效率要更高些。

(未完待續)

下一篇:Python3 如何優雅地使用正則表示式(詳解二)