Python3 如何優(yōu)雅地使用正則表達(dá)式(詳解一)
正則表達(dá)式介紹
?????? 正則表達(dá)式(Regular
expressions 也稱為 REs,或 regexes 或 regex
patterns)本質(zhì)上是一個(gè)微小的且高度專業(yè)化的編程語(yǔ)言。它被嵌入到 Python 中,并通過(guò) re
模塊提供給程序猿使用。使用正則表達(dá)式,你需要指定一些規(guī)則來(lái)描述那些你希望匹配的字符串集合。
??????? 正則表達(dá)式模式被編譯成一系列的字節(jié)碼,然后由一個(gè) C
語(yǔ)言寫(xiě)的匹配引擎所執(zhí)行。對(duì)于高級(jí)的使用,你可能需要更關(guān)注匹配引擎是如何執(zhí)行給定的 RE,并通過(guò)一定的方式來(lái)編寫(xiě)
RE,以便產(chǎn)生一個(gè)可以運(yùn)行得更快的字節(jié)碼。本文暫不講解優(yōu)化的細(xì)節(jié),因?yàn)檫@需要你對(duì)匹配引擎的內(nèi)部機(jī)制有一個(gè)很好的理解。但本文的例子均是符合標(biāo)準(zhǔn)的正則表達(dá)式語(yǔ)法。
?????? 正則表達(dá)式語(yǔ)言相對(duì)較小,并且受到限制,所以不是所有可能的字符串處理任務(wù)都可以使用正則表達(dá)式來(lái)完成。還有一些特殊的任務(wù),可以使用正則表達(dá)式來(lái)完成,但是表達(dá)式會(huì)因此而變得非常復(fù)雜。在這種情況下,你可能通過(guò)自己編寫(xiě) Python 代碼來(lái)處理會(huì)更好些;盡管 Python 代碼比一個(gè)精巧的正則表達(dá)式執(zhí)行起來(lái)會(huì)慢一些,但可能會(huì)更容易理解。

字符匹配
????? 大多數(shù)字母和字符會(huì)匹配它們自身。舉個(gè)例子,正則表達(dá)式bilibili 將完全匹配字符串?"bilibili"。同時(shí)我們可以采用不區(qū)分大小寫(xiě)方式使得我們可以匹配“BILIBILI”。
元字符
?????? 有少數(shù)特殊的字符我們稱之為元字符(metacharacter),它們并不能匹配自身,它們定義了字符類、子組匹配和模式重復(fù)次數(shù)等。下邊是元字符的完整列表:
.? ?^? ?$? ?*? ?+? ??? ?{ }? ?[ ]? ?\? ?|? ?( )
(一)首先介紹一下“.”
?????? 它匹配除了換行符以外的任何字符。如果設(shè)置了 re.DOTALL 標(biāo)志,.?將匹配包括換行符在內(nèi)的任何字符。??例子如下所示。

(二)下面介紹一下“^”
?????? 當(dāng)我們需要匹配方括號(hào)中未列出的所有其他字符時(shí),通常我們采用的方法是在類的開(kāi)頭添加一個(gè)脫字符號(hào)?^?,例如?[^8]?會(huì)匹配除了?'8'?之外的任何字符。例子如下所示。

(三)下面介紹一下“$”
??????? $可以用來(lái)匹配輸入字符串的結(jié)束位置。整個(gè)字符串結(jié)束字符,但如果最后一個(gè)是換行符那按照倒數(shù)第二個(gè)也可以匹配到,當(dāng)然直接按照換行符也可以匹配到。需要注意的一點(diǎn)是:元字符在方括號(hào)中不會(huì)觸發(fā)“特殊功能”,在字符類中,它們只匹配自身。例如?[CZY$]?會(huì)匹配任何字符?'C','Z','Y'?或?'$','$'?是一個(gè)元字符,但在方括號(hào)中它不表示特殊含義,它只匹配?'$'?字符本身。


(四)接下來(lái)介紹一下“*”
?????? “*”能夠輕松的匹配不同的字符集合,但 Python 字符串現(xiàn)有的方法卻無(wú)法實(shí)現(xiàn)。然而天無(wú)絕人之路,正則表達(dá)式有另一個(gè)強(qiáng)大的功能,就是你可以指定 RE 部分被重復(fù)的次數(shù)。
?????? 我們來(lái)看看?*?這個(gè)元字符,當(dāng)然它不是匹配?'*'?字符本身,它用于指定前一個(gè)字符匹配零次或者多次。
?????? 例如?ca*t?將匹配?ct(0
個(gè)字符 a),cat(1 個(gè)字符 a),caaat(3 個(gè)字符 a),等等。需要注意的是,由于受到 C 語(yǔ)言的 int
類型大小的內(nèi)部限制,正則表達(dá)式引擎會(huì)限制字符 'a' 的重復(fù)個(gè)數(shù)不超過(guò) 20 億個(gè);不過(guò),通常我們工作中也用不到那么大的數(shù)據(jù)。

(五)然后介紹一下“+”
?????? 元字符是?+可以用于指定前一個(gè)字符匹配一次或者多次。要特別注意?*?和?+?的區(qū)別:*?匹配的是零次或者多次,所以被重復(fù)的內(nèi)容可能壓根兒不會(huì)出現(xiàn);+?至少需要出現(xiàn)一次。例如?ca+t?會(huì)匹配?cat?和?caaat,但不會(huì)匹配?ct。

(六)下面我們介紹一下“{}”
?????? 最靈活的應(yīng)該是元字符?{m, n}(m 和 n 都是十進(jìn)制整數(shù)),上邊講到的幾個(gè)元字符都可以使用它來(lái)表達(dá),它的含義是前一個(gè)字符必須匹配 m 次到
n 次之間。例如?a/{1,
3}b 會(huì)匹配配?a/b,a//b?和?a///b。但不會(huì)匹配?ab;也不會(huì)匹配a////b。
??? ?? 我們可以省略 m 或者 n,這樣的話,引擎會(huì)假定一個(gè)合理的值代替。省略 m,將被解釋為下限 0;省略 n 則會(huì)被解釋為無(wú)窮大。例如 {, n} 相當(dāng)于 {0, n};如果是 {m, } 相當(dāng)于 {m, +無(wú)窮};如果是 {n},則是重復(fù)前一個(gè)字符 n 次。
??????? 其實(shí)?*、+?和???都可以使用?{m, n}?來(lái)代替。{0,}?跟?*?是一樣的;{1, }?跟?+?是一樣的;{0, 1}跟???是一樣的。不過(guò)我們還是盡量使用?*、+?和??,因?yàn)檫@些字符更短并且更容易閱讀。

(七)下面我們介紹一下“[]”
?????? 它可以指定一個(gè)字符類用于存放你需要匹配的字符集合??梢詥为?dú)列出需要匹配的字符,也可以通過(guò)兩個(gè)字符和一個(gè)橫桿?-?指定匹配的范圍。例如?[abc]?會(huì)匹配字符?a,b?或?c;[a-c]?可以實(shí)現(xiàn)相同的功能。后者使用范圍來(lái)表示與前者相同的字符集合。如果你想只匹配小寫(xiě)字母,你的 RE 可以寫(xiě)成?[a-z]。

(八)下面我們介紹一下“\”
?????? 或許最重要的元字符當(dāng)屬反斜杠?\?了。跟 Python 的字符串規(guī)則一樣,如果在反斜杠后邊緊跟著一個(gè)元字符,那么元字符的“特殊功能”也不會(huì)被觸發(fā)。例如你需要匹配符號(hào)?[?或?\,你可以在它們前面加上一個(gè)反斜杠,以消除它們的特殊功能:\[,\\。
??? ?? 反斜杠后邊跟一些字符還可以表示特殊的意義,例如表示十進(jìn)制數(shù)字,表示所有的字母或者表示非空白的字符集合。
?????? 舉例說(shuō)明:\w?匹配任何字符。如果正則表達(dá)式以字節(jié)的形式表示,這相當(dāng)于字符類?[a-zA-Z0-9_];如果正則表達(dá)式是一個(gè)字符串,\w?會(huì)匹配所有
Unicode 數(shù)據(jù)庫(kù)中標(biāo)記為字母的字符。你可以在編譯正則表達(dá)式的時(shí)候,通過(guò)提供 re.ASCII
表示進(jìn)一步限制?\w?的定義。
????? 下邊列舉一些反斜杠加字符構(gòu)成的特殊含義:
?????? 特殊字符含義\d匹配任何十進(jìn)制數(shù)字;相當(dāng)于類?[0-9]\D與?\d?相反,匹配任何非十進(jìn)制數(shù)字的字符;相當(dāng)于類?[^0-9]\s匹配任何空白字符(包含空格、換行符、制表符等);相當(dāng)于類?[
\t\n\r\f\v]\S與?\s?相反,匹配任何非空白字符;相當(dāng)于類?[^
\t\n\r\f\v]\w匹配任何字符,\W與 \w?相反\b匹配單詞的開(kāi)始或結(jié)束\B與?\b?相反。

?????? 它們可以包含在一個(gè)字符類中,并且一樣擁有特殊含義。例如?[\s,.]?是一個(gè)字符類,它將匹配任何空白字符(/s?的特殊含義),','?或?'.'。
(九)下面我們介紹一下“|”
?????? “|”單獨(dú)使用只匹配左右緊鄰的表達(dá)式,可以和()結(jié)合使用。以下我以匹配IP地址為例解釋一下“|”的用法。
