正則表達(dá)式學(xué)習(xí),請(qǐng)參考這篇
正則表達(dá)式(Regular Expression, RE)就是一組定義某種搜索模式(pattern)的字符。
最簡(jiǎn)單的 RE 例子如下。
'steven'很明顯,這樣一個(gè) RE 只能搜索出含 steven 的字符串。
你可以用 Python 代碼來驗(yàn)證,但現(xiàn)在假設(shè)我們還不會(huì)寫,我們可以去?https://regex101.com/ 來驗(yàn)證。如下圖右上角所示,匹配成功。

這樣來搜索未免太傻了,有沒有稍微智能一點(diǎn)的方法。再看下面的 RE。
^s....n$上面的 RE 定義的模式如下:任何 6 個(gè)字符的單詞,以 s 開頭 (^s 的效果),以 n 收尾 (n$ 的效果)。之所以是 6 個(gè)字符,是因?yàn)橛?4 個(gè)點(diǎn) (.) 加上 s 和 n 字符。用上面那個(gè)網(wǎng)站做驗(yàn)證,這個(gè) RE ^s....n$?的若干匹配結(jié)果如下:
seven:不匹配(五個(gè)字母)
strong man:不匹配(十個(gè)字母加空格)
soften:匹配
steven:匹配
Steven:不匹配
看最后兩個(gè) steven 和 Steven,區(qū)別是第一個(gè)字母的大小寫,如果我想匹配兩者怎么辦呢?用下面的 RE
^[s|S]....n$中括號(hào) [] 表示一個(gè)集合,而 | 分隔集合里面的元素,在本例是 s 和 S。意思就是匹配開頭的 s 或 S,結(jié)尾是 n 的 6 字符的單詞。?


這樣每次固定單詞長(zhǎng)度也不太智能吧(比如長(zhǎng)度為 n 就需要手動(dòng)輸入 n 個(gè)點(diǎn) .),開頭 s 結(jié)尾 n 的單詞好多呢,我如果都想搜索出來該怎么辦呢?看下面的 RE
^s[a-z]+n$現(xiàn)在 sun 和 strengthen 都可以匹配出來了。起作用的是 [a-z]+,[a-z] 表示小寫的字母 a 到 z 的集合,而 + 代表大于一次,聯(lián)合在一起的意思就是該單詞“以 s 開頭,以 n 結(jié)尾,中間有大于一個(gè)的任何小寫字母”。


但上述模式對(duì)單詞 self-restrain 不起作用,因?yàn)橛袀€(gè)短連接線(hyphen)。

沒關(guān)系,我們把 - 加入字母集合里,寫成 [a-z-]+,注意第一個(gè) - 表示從 a 到 z,第二個(gè) - 表示短連接線。現(xiàn)在可以匹配 self-restrain 了。

目前對(duì) RE 有點(diǎn)感覺了吧,即便不會(huì)確切的表示也沒關(guān)系,因?yàn)檫@就是本帖要介紹的。還是那句話,興趣最重要,有興趣才能有效的往下看。
本帖結(jié)構(gòu)如下:
原始字符串
五類元字符
七個(gè)函數(shù)
三個(gè)實(shí)例
注:本帖里的 RE 可視化可參考鏈接 https://www.debuggex.com/。
原始字符串(raw string)是所有的字符串都是直接按照字面的意思來使用,沒有轉(zhuǎn)義特殊或不能打印的字符,通常簡(jiǎn)稱為 r-string。
如果沒有轉(zhuǎn)義字符,原始字符串和普通字符串是一樣的,比如
print('hello')print(r'hello')
hello
hello如果有轉(zhuǎn)義字符(用反斜線 \),原始字符串和普通字符串是不一樣的,比如
print('\blake')print(r'\blake')
lake
\blake因此,不管什么時(shí)候用原始字符串準(zhǔn)沒錯(cuò)。
元字符(meta character)就是一種自帶特殊含義的字符,也叫特殊字符。比如 [] * + ? {} | () . ^ $ \,原字符按用途可分五類:
表示集合:[]
表示次數(shù):* + ? {}
表示并列:|
用于提取:()
用于轉(zhuǎn)義:. ^ $ \
首先定義一個(gè)函數(shù),當(dāng)在句子(是個(gè)字符串 str)沒有發(fā)現(xiàn)模式 pat 時(shí),返回“沒有找到”,反之打印出所有符合模式的子字符串。
import redef look_for(pat, str):return '沒有找到' if re.search(pat, str) is Noneelse re.findall(pat, str)
上面代碼中的 re 是 Python 中正則表達(dá)式的庫(kù),而 search 和 findall 是包里的兩個(gè)函數(shù),顧名思義它們做的就是搜索和找出全部的意思,第三節(jié)會(huì)詳解講。
2.1
集合字符
中括號(hào)表示一個(gè)字符集,即創(chuàng)建的模式匹配中括號(hào)里指定字符集中的任意一個(gè)字符,字符集有三種方式來表現(xiàn):
明確字符:[abc] 會(huì)匹配字符 a,b 或者 c
范圍字符:[a-z] 會(huì)匹配字符 a 到 z
補(bǔ)集字符:[^6]?會(huì)匹配除了 6 以外的字符
下面我們來一一細(xì)看。
匹配中括號(hào)里面的任意一個(gè)字符。
pat = r'[abc]'print( look_for(pat, 'a') )print( look_for(pat, 'ac') )print( look_for(pat, 'cba') )print( look_for(pat, 'steven') )
['a']
['a', 'c']
['c', 'b', 'a']
沒有找到分析如下:
該模式只匹配字符 a,b 或者 c,因此前三個(gè)例子的字符串里都有相應(yīng)字符匹配,而最后例子里的 steven 不包含 a, b 或 c。
模式?[abc]? 的可視圖如下,注意 “One of” 是說集合里面的字符是“或”的關(guān)系。

在 [ ]?中加入 - 即可設(shè)定范圍,比如
[a-e]?=?[abcde]
[1-4]?=?[1234]
[a-ep]?=?[abcdep]
[0-38]?=?[01238]
看兩個(gè)例子。
print( look_for(r'[a-ep]', 'person') )print( look_for(r'[0-38]', '666') )
['p', 'e']
沒有找到分析如下:
例一的模式等價(jià)于?[abcdep],匹配單詞 person 里面的 p 和 e 字符。
例二的模式等價(jià)于?[01238],不匹配單詞 666 里面的任何字符。
模式?[a-ep]?和 [0-38]?的可視圖如下。


在?[ ]?中加入?^?即可除去后面的字符集,比如
[^abc] 就是非 a, b, c 的字符
[^123] 就是非 1, 2, 3 的字符
看四個(gè)例子。
print( look_for(r'[^abc]', 'baba') )print( look_for(r'[^abc]', 'steven') )print( look_for(r'[^123]', '456') )print( look_for(r'[^123]', '1+2=3') )
沒有找到
['s', 't', 'e', 'v', 'e', 'n']
['4', '5', '6']
['+', '=']分析如下:
例一 baba 里面所有字母不是?a 就是?b,因此沒有匹配
例二 steven 所有字母都不是?a, b 和 c,因此全部匹配
例三?456?所有字母不是 1,2 和?3,因此全部匹配
例四?1+2=3?有 +號(hào)和 =號(hào)不是 1, 2?和 3,因此它倆匹配
模式?[^abc]?和?[^123]?的可視圖如下。注意 “None of” 是說集合里面的字符是的補(bǔ)集。


2.2
次數(shù)字符
上面的模式有個(gè)致命短板,就是只能匹配一個(gè)字符!這在實(shí)際應(yīng)用幾乎沒用,因此我們需要某些帶有“次數(shù)功能”的元字符,如下:
貪婪模式:
*?表示后面可跟 0 個(gè)或多個(gè)字符
+?表示后面可跟 1 個(gè)或多個(gè)字符
??表示后面可跟 0 個(gè)或 1 個(gè)字符
非貪婪模式:
*??表示后面可跟 0 個(gè)或多個(gè)字符,但只取第一個(gè)
+??表示后面可跟 1 個(gè)或多個(gè)字符,但只取第一個(gè)
???表示后面可跟 0 個(gè)或 1 個(gè)字符,但只取第一個(gè)
貪婪模式和非貪婪模式的區(qū)別在下面講?? 的時(shí)候會(huì)介紹。
首先定義“字符 u 可以出現(xiàn) 0 次或多次”的模式,結(jié)果不需要解釋。
pat = r'colou*r'print( look_for(pat, 'color') )print( look_for(pat, 'colour') )print( look_for(pat, 'colouuuuuur') )
['color']
['colour']
['colouuuuuur']模式?colou*r?的可視圖如下。

注意?u 附近有三條通路
上路跳過?u,代表零個(gè) u
中路穿越?u,代表一個(gè) u
下路循環(huán)?u,代表多個(gè) u
首先定義“字符 u 可以出現(xiàn) 1?次或多次”的模式,結(jié)果不需要解釋。
pat = r'colou+r'print( look_for(pat, 'color') )print( look_for(pat, 'colour') )print( look_for(pat, 'colouuuuuur') )
沒有找到
['colour']
['colouuuuuur']模式?colou+r?的可視圖如下。

注意?u 附近有兩條通路
中路穿越?u,代表一個(gè)?u
下路循環(huán)?u,代表多個(gè)?u
首先定義“字符 u 可以出現(xiàn) 0 次或 1 次”的模式,結(jié)果不需要解釋。
pat = r'colou?r'print( look_for(pat, 'color') )print( look_for(pat, 'colour') )print( look_for(pat, 'colouuuuuur') )
['color']
['colour']
沒有找到模式?colou+r?的可視圖如下。

注意?u 附近有兩條通路
上路跳過?u,代表零個(gè)?u
中路穿越?u,代表一個(gè)?u
有的時(shí)候一個(gè)句子里會(huì)有重復(fù)的字符,假如是 > 字符,如果我們要匹配這個(gè)>,到底在哪一個(gè) > 就停止了呢?
這個(gè)就是貪心(greedy)模式和非貪心(non-greedy)模式的區(qū)別,讓我們來看個(gè)例子。
heading = r'TITLE
'如果模式是 <.+>,那么我們要獲取的就是以 < 開頭,以 > 結(jié)尾,中間有 1 個(gè)或多個(gè)字符的字符串。這里我們先提前介紹 . 字符,它是一個(gè)通配符,可以代表任何除新行 (\n) 的字符。
pat = r'<.+>'print( look_for(pat, heading) )
['<h1>TITLEh1>']結(jié)果如上,獲取的字符串確實(shí)以 < 開頭,以 > 結(jié)尾,但是仔細(xì)看下,其實(shí)在? heading[3] 出也是 >,為什么沒有匹配到它而是匹配到最后一個(gè) > 呢?
原因就是上面用了貪婪模式,即在整個(gè)表達(dá)式匹配成功的前提下,盡可能多的匹配。那么其對(duì)立的非貪婪模式,就是在整個(gè)表達(dá)式匹配成功的前提下,盡可能少的匹配。
實(shí)現(xiàn)非貪婪模式只需在最后加一個(gè) ? 字符,代碼如下:
pat = r'<.+?>'print( look_for(pat, heading) )
['', '
']結(jié)果無(wú)需解釋。
有意思的是,模式?<.+> 和?<.+?> 的可視化圖長(zhǎng)得一樣,如下。這樣我們就無(wú)法從圖上分辨是否使用貪婪或非貪婪的模式了,只能從代碼中識(shí)別了。

有的時(shí)候我們非常明確要匹配的字符出現(xiàn)幾次,比如
中國(guó)的手機(jī)號(hào)位數(shù)是 13 位,n = 13
密碼需要 8 位以上,n?≥ 8
公眾號(hào)文章標(biāo)題長(zhǎng)度不能超過 64,n?≤ 64
用戶名需要在 8 到 16 位之間,8 ≤ n?≤ 16
這時(shí)我們可以設(shè)定具體的上界或(和)下界,使得代碼更加有效也更好讀懂,規(guī)則如下:
{n} 左邊的字符串是否出現(xiàn) n 次
{n, } 左邊的字符串是否出現(xiàn)大于等于 n 次
{, n} 左邊的字符串是否出現(xiàn)小于等于 n 次
{n, m} 左邊的字符串是否出現(xiàn)在 n 次和 m 次之間
用規(guī)則來看例子,很容易看懂。
s = 'a11bbb2222ccccc'print( look_for(r'[a-z]{1}', s) )print( look_for(r'[0-9]{2,}', s) )print( look_for(r'[a-z]{,5}', s) )print( look_for(r'[0-9]{2,4}', s) )
['a', 'b', 'b', 'b', 'c', 'c', 'c', 'c', 'c']
['11', '2222']
['a', '', '', 'bbb', '', '', '', '', 'ccccc', '']
['11', '2222']需要解釋的是例三,匹配五個(gè)以下的 a 到 z 小寫字母,當(dāng)然也包括零個(gè),因此結(jié)果包含那些空字符。
模式 {n}, { ,n}, {n, } 和 {n, m} 的可視圖如下:

上面都是貪婪模式,當(dāng)然也有其對(duì)應(yīng)的非貪婪模式,但只有?{n, m}??有意義,原因自己想。上面的模式對(duì)于前一個(gè)字符重復(fù) m 到 n 次,并且取盡可能少的情況。比如在字符串'sssss'中,s{2,4} 會(huì)匹配 4 個(gè) s,但 s{2,4}? 只匹配 2 個(gè) s。
2.3
并列字符
字符集合問題解決了,字符次數(shù)問題解決了,如果現(xiàn)在面臨的問題著是匹配 A 或 B 其中一個(gè)呢?用垂線 | 字符,A|B,如果 A 匹配了,則不再查找 B,反之亦然。
首先定義“句子出現(xiàn) like 或 love 一詞”的模式。
pat = r'like|love'print( look_for(pat, 'like you') )print( look_for(pat, 'love you') )
['like']
['love']模式?like|love?的可視圖如下,其并列模式體現(xiàn)在黑點(diǎn)到白點(diǎn)的并行通路上。

2.4
提取字符
如果你想把匹配的內(nèi)容提取出來,用小括號(hào),而在小括號(hào)里面你可以設(shè)計(jì)任意正則表達(dá)式。
首先定義“beat 的第三人稱,過去式,過去分詞和現(xiàn)在進(jìn)行式”的模式,為了獲取 beat 加正確后綴的所有單詞。
pat = r'beat(s|ed|en|ing)'print( look_for(pat, 'beats') )print( look_for(pat, 'beated') )print( look_for(pat, 'beaten') )print( look_for(pat, 'beating') )
['s']
['ed']
['en']
['ing']我們將出現(xiàn)在 () 里面的后綴都獲取出來了,其可視圖如下,我們發(fā)現(xiàn)“Group 1”代表?() 起的作用。?

但其實(shí)這不是我們想要的,我們想把帶著后綴的 beat 給獲取出來。那么只有在最外面再加一層 (),模式如下。
pat = r'(beat(s|ed|en|ing))'print( look_for(pat, 'beats') )print( look_for(pat, 'beated') )print( look_for(pat, 'beaten') )print( look_for(pat, 'beating') )
[('beats', 's')]
[('beated', 'ed')]
[('beaten', 'en')]
[('beating', 'ing')]其可視圖如下,我們發(fā)現(xiàn) Group 2 嵌套在 Group 1 里面。

現(xiàn)在帶著后綴的 beat?已經(jīng)獲取出來了,上面列表中每個(gè)元組的第一個(gè)元素,但完美主義者不想要后綴(即元組的第二個(gè)元素),可以用下面的騷模式。
在 () 中最前面加入 ?:。(?:)?代表只匹配不獲取(non-capturing),結(jié)果看上去非常自然。
pat = r'(beat(?:s|ed|en|ing))'print( look_for(pat, 'beats') )print( look_for(pat, 'beated') )print( look_for(pat, 'beaten') )print( look_for(pat, 'beating') )
['beats']
['beated']
['beaten']
['beating']其可視圖如下,我們發(fā)現(xiàn)只有一個(gè) Group 1,那個(gè)內(nèi)括號(hào),代表不被獲取的內(nèi)容,沒有體現(xiàn)在下圖中。

2.5
轉(zhuǎn)義字符
字符集合問題解決了,字符次數(shù)問題解決了,字符并列問題解決了,字符獲取問題解決了,看上去我們能做很多事了。別急,RE 給你最后一擊,轉(zhuǎn)義字符,讓模式更加強(qiáng)大。
轉(zhuǎn)義字符,顧名思義,就是能轉(zhuǎn)換自身含義的字符。
點(diǎn) . 不再是點(diǎn),美元 $ 不再是美元,等等等等。。。
點(diǎn) . 表示除新行(newline)的任意字符,它是個(gè)通配符。用它最無(wú)腦簡(jiǎn)便,但是代碼也最難讀懂,效率也最低下。
定義“含有 1 個(gè)或多個(gè)非新行字符”的模式。
pat = r'.+'print( look_for(pat, 'a') )print( look_for(pat, 'b1') )print( look_for(pat, 'C@9') )print( look_for(pat, '$ 9_fZ') )print( look_for(pat, '9z_\t\r\n') )
['a']
['b1']
['C@9']
['$ 9_fZ']
['9z_\t\r']除了最后例子中的 \n 沒有匹配到,其他的字符全部匹配出來。
托字符?^?表示字符串開頭。
定義“以 s?開頭字符串”的模式。
pat = r'^s[\w]*'print( look_for(pat, 'son') )print( look_for(pat, 'shot') )print( look_for(pat, 'come') )
['son']
['shot']
沒有找到結(jié)果太明顯,不解釋。
美元符 $?表示字符串結(jié)尾。
定義“以 s?結(jié)尾字符串”的模式。
pat = r'[\w]*s$'print( look_for(pat, 'yes') )print( look_for(pat, 'mess') )print( look_for(pat, 'come') )
['yes']
['mess']
沒有找到結(jié)果太明顯,不解釋。
更厲害的是,反斜杠?\?可對(duì)特殊字符進(jìn)行轉(zhuǎn)義,也可對(duì)普通字符轉(zhuǎn)義。
將特殊字符轉(zhuǎn)成自身含義:用 \ 作用在 ^ . \ 等身上,代表乘方 \^、小數(shù)點(diǎn) \.?和除號(hào) \\
將自身字符轉(zhuǎn)成特殊含義:用 \ 作用在 w d n 等身上,代表字母 \w、數(shù)字 \d 和新行 \n
在反斜杠的限制下,$ 終于代表美元了!
pat = r'\$[0-9.]+'print( look_for(pat, 'it costs $99.99') )['$99.99']在反斜杠的限制下,?^ . \??終于代表乘方、小數(shù)點(diǎn)和除號(hào)了!
pat = r'(\\|\/|\^|\.)'print( look_for(pat, '(5/2)^2=6.25') )['/', '^', '.']沒有了反斜杠的限制,一切亂了套,點(diǎn) . 就是通配符,可以匹配字符串里所有字符。
pat = r'(\|/|^|.)'print( look_for(pat, '(5/2)^2=6.25') )['', '(', '5', '/', '2', ')', '^', '2', '=', '6', '.', '2', '5']但如果在中括號(hào) [] 集合里,每個(gè)字符就是它本身的意義,點(diǎn)就是點(diǎn),而不是通配符。
pat = r'[/^\.]'print( look_for(pat, '(5/2)^2=6.25') )['/', '^', '.']規(guī)則總結(jié)如下(大寫和小寫互補(bǔ),兩者加一起是全集):
\b:匹配空字符串,但僅適用于單詞的“首尾”
\B:匹配空字符串,但僅適用于單詞的“非首尾”
\d:匹配任何“數(shù)字”字符,等價(jià)于 [0-9]
\D:匹配任何“非數(shù)字”字符,等價(jià)于 [^0-9]
\s:匹配任何“空白”字符,等價(jià)于 [\t\n\r]
\S:匹配任何“非空白”字符,等價(jià)于 [^\t\n\r]
\w:匹配任何“字母數(shù)字下劃線”字符,等價(jià)于 [a-zA-Z0-9_]
\W:匹配任何“非字母數(shù)字下劃線”字符,等價(jià)于 [^a-zA-Z0-9_]
\A:匹配句子的“開頭”字符,等價(jià)于 ^
\Z:匹配句子的“結(jié)尾”字符,等價(jià)于 $
\t:匹配句子的“制表鍵 (tab)”字符
\r:匹配句子的“回車鍵 (return)”字符
\n:匹配句子的“換行鍵 (newline)”字符
\b?\B
pat = r'\blearn\b'print( look_for(pat, 'learn Python') )print( look_for(pat, 'relearn Python') )print( look_for(pat, 'learning Python') )print( look_for(pat, 'relearning Python') )
['learn']
沒有找到
沒有找到
沒有找到\b 只能抓住 learn 前后的首尾空字符,那么只能匹配不帶前綴和后綴的 learn,? 即 learn 本身。
pat = r'\Blearn\B'print( look_for(pat, 'learn Python') )print( look_for(pat, 'relearn Python') )print( look_for(pat, 'learning Python') )print( look_for(pat, 'relearning Python') )
沒有找到
沒有找到
沒有找到
['learn']\B?只能抓住 learn 前后的非首尾空字符,那么只能匹配帶前綴和后綴的 learn,即 relearning。
pat = r'\blearn\B'print( look_for(pat, 'learn Python') )print( look_for(pat, 'relearn Python') )print( look_for(pat, 'learning Python') )print( look_for(pat, 'relearning Python') )
沒有找到
沒有找到
['learn']
沒有找到learn 前 \b 后 \B,只能匹配帶后綴的 learn,即 learning。
pat = r'\Blearn\b'print( look_for(pat, 'learn Python') )print( look_for(pat, 'relearn Python') )print( look_for(pat, 'learning Python') )print( look_for(pat, 'relearning Python') )
沒有找到
['learn']
沒有找到
沒有找到learn?前?\B?后?\b,只能匹配帶前綴的 learn,即 relearn。
\d?\D
pat = r'\d+'print( look_for(pat, '12+ab34-cd56*ef78/gh90%ij') )
['12', '34', '56', '78', '90']匹配數(shù)字,不解釋。
pat = r'\D+'print( look_for(pat, '12+ab34-cd56*ef78/gh90%ij') )
['+ab', '-cd', '*ef', '/gh', '%ij']匹配非數(shù)字,不解釋。
\s?\S
pat = r'\s+'s = '''please don'tleave mealone'''print( look_for(pat, s) )
[' ', '\n', ' ', '\n ']匹配各種空格比如制表、回車或新行,不解釋。
pat = r'\S+'print( look_for(pat, s) )
['please', "don't", 'leave', 'me', 'alone']匹配各種非空格,不解釋。
\w \W
pat = r'\w+'print( look_for(pat, '12+ab_34-cd56_ef78') )
['12', 'ab_34', 'cd56_ef78']匹配字母數(shù)字下劃線,不解釋。
pat = r'\W+'print( look_for(pat, '12+ab_34-cd56_ef78') )
['+', '-']匹配非字母數(shù)字下劃線,不解釋。
\A \Z
pat1 = r'^y[\w]*'pat2 = r'\Ay[\w]*'str1 = 'you rock'str2 = 'rock you'print( look_for(pat1, str1) )print( look_for(pat2, str1) )print( look_for(pat1, str2) )print( look_for(pat2, str2) )
['you']
['you']
沒有找到
沒有找到匹配開頭字符,\A 和 ^ 等價(jià),不解釋。
pat1 = r'[\w]*k$'pat2 = r'[\w]*k\Z'str1 = 'you rock'str2 = 'rock you'print( look_for(pat1, str1) )print( look_for(pat2, str1) )print( look_for(pat1, str2) )print( look_for(pat2, str2) )
['rock']
['rock']
沒有找到
沒有找到匹配結(jié)尾字符,\Z?和?$?等價(jià),不解釋。
了解完上節(jié)介紹的元字符的基本知識(shí),本節(jié)的函數(shù)運(yùn)用就很簡(jiǎn)單了。RE 包里常見的函數(shù)總結(jié)如下:
match(pat, str):檢查字符串的開頭是否符合某個(gè)模式
search(pat, str):檢查字符串中是否符合某個(gè)模式
findall(pat, str):返回所有符合某個(gè)模式的字符串,以列表形式輸出
finditer(pat, str):返回所有符合某個(gè)模式的字符串,以迭代器形式輸出
split(pat, str):以某個(gè)模式為分割點(diǎn),拆分整個(gè)句子為一系列字符串,以列表形式輸出
sub(pat, repl, str):句子 str 中找到匹配正則表達(dá)式模式的所有子字符串,用另一個(gè)字符串 repl 進(jìn)行替換
compile(pat):將某個(gè)模式編譯成對(duì)象,供之后使用
match(pat, str)
判斷模式是否在字符串開頭位置匹配。如果匹配,返回對(duì)象,如果不匹配,返回 None。
s = 'Kobe Bryant'print( re.match(r'Kobe', s) )print( re.match(r'Kobe', s).group() )print( re.match(r'Bryant', s) )
<re.Match?object; span=(0,?4), match='Kobe'>
Kobe
None該函數(shù)返回的是個(gè)對(duì)象(包括匹配的子字符串和在句中的位置索引),如果只需要子字符串,需要用 group() 函數(shù)。
由于值匹配句頭,那么句中的 Bryant 無(wú)法被匹配到。
search(pat, str)
在字符串中查找匹配正則表達(dá)式模式的位置。如果匹配,返回對(duì)象,如果不匹配,返回 None。
s = 'Kobe Bryant'print( re.search(r'Kobe', s) )print( re.search(r'Kobe', s).group() )print( re.search(r'Bryant', s) )print( re.search(r'Bryant', s).group() )
<re.Match?object; span=(0,?4), match='Kobe'>
Kobe
<re.Match?object; span=(5,?11), match='Bryant'>
Bryant該函數(shù)返回的是個(gè)對(duì)象(包括匹配的子字符串和在句中的位置索引),如果只需要子字符串,需要用 group() 函數(shù)。
如果句子出現(xiàn)兩個(gè) Bryant 呢?
s = 'Kobe Bryant loves Gianna Bryant'print( re.search(r'Bryant', s) )print( re.search(r'Bryant', s).group() )print( re.search(r'Bryant', s) )
<re.Match?object; span=(5,?11), match='Bryant'>
Bryant
<re.Match?object; span=(5,?11), match='Bryant'>根據(jù)結(jié)果只匹配出第一個(gè),我們需要下面的函數(shù)來匹配全部。
findall(pat, str)
在字符串中找到正則表達(dá)式所匹配的所有子串,并組成一個(gè)列表返回。
s = 'Kobe Bryant loves Gianna Bryant'print( re.findall(r'Kobe', s) )print( re.findall(r'Bryant', s) )print( re.findall(r'Gigi', s) )
['Kobe']
['Bryant', 'Bryant']
[]結(jié)果不解釋。
finditer(pat, str)
和 findall 類似,在字符串中找到正則表達(dá)式所匹配的所有子串,并組成一個(gè)迭代器返回。
s = 'Kobe Bryant loves Gianna Bryant'print( [i for i in re.finditer(r'Kobe', s)] )print( [i for i in re.finditer(r'Bryant', s)] )print( [i for i in re.finditer(r'Gigi', s)] )
[0, 4), match='Kobe'>]
[5, 11), match='Bryant'>,
?25, 31), match='Bryant'>]
[] 如果需要匹配子串在原句中的位置索引,用 finditer,此外用 findall。
split(pat, str)
將字符串匹配正則表達(dá)式的部拆分開并返回一個(gè)列表。
s = 'Kobe Bryant loves Gianna Bryant'print( re.split(r'\s', s) )
['Kobe', 'Bryant', 'loves', 'Gianna', 'Bryant']按空格拆分,不解釋。
sub(pat, repl, str)
句子 str 中找到匹配正則表達(dá)式模式的所有子字符串,用另一個(gè)字符串 repl 進(jìn)行替換。如果沒有找到匹配模式的串,則返回未被修改的句子 str,其中 repl 既可以是字符串也可以是一個(gè)函數(shù)。
s = 'Kobe Bryant loves Gianna Bryant'print( re.sub(r'\s', '-', s) )
Kobe-Bryant-loves-Gianna-Bryant用 - 代替空格。
print( re.sub(r'Gianna', 'Gigi', s) )Kobe?Bryant loves Gigi Bryant用?Gigi?代替 Gianna。
print( re.sub(r'\d+', '_', s) )Kobe?Bryant loves Gianna Bryant用?_?代替數(shù)字(一個(gè)或多個(gè)),但句中沒有數(shù)字,因此沒用替代動(dòng)作。
print( re.sub(r'\d*', '_', s)_K_o_b_e_?_B_r_y_a_n_t_ _l_o_v_e_s_ _G_i_a_n_n_a_ _B_r_y_a_n_t_用?_?代替數(shù)字(零個(gè)或多個(gè)),雖然句中沒有數(shù)字,但是零個(gè)數(shù)字就是空字符,因此 _ 替代所有空字符。好玩吧 :)
compile(pat)
把正則表達(dá)式的模式轉(zhuǎn)化成正則表達(dá)式對(duì)象,供其他函數(shù)如match?和 search?使用。對(duì)象創(chuàng)建出來可以循環(huán)使用,如果某種模式要重復(fù)使用話,用“先 compile 再 findall”的方式更加高效。
用處理電郵地址來舉例。
email = '''Shengyuan Personal: [email protected]Shengyuan Work: [email protected]Shengyuan School: [email protected]Obama: [email protected]'''print(email)
Shengyuan Personal:?quantsteven@gmail.com
Shengyuan Work: shengyuan@octagon-advisors.com
Shengyuan School:[email protected]
Obama:[email protected]創(chuàng)建電郵的模式 r'[\w.-]+@[\w.-]+',用 compile 先創(chuàng)建 RE 對(duì)象,供之后使用。
pat = r'[\w.-]+@[\w.-]+'obj = re.compile(pat)obj
re.compile(r'[\w.-]+@[\w.-]+', re.UNICODE)在對(duì)象 obj 上分別使用?match,?search, findall, findieter?等方法,結(jié)果如下:
print( obj.match(email), '\n')print( obj.search(email), '\n' )print( obj.findall(email), '\n' )print( [i for i in obj.finditer(email)])
None
<re.Match?object; span=(20,?41), match='[email protected]'>?
['[email protected]',
?'[email protected]',
?'[email protected]',
?'[email protected]']
[<re.Match?object; span=(20,?41), match='[email protected]'>,
<re.Match?object; span=(58,?88), match='[email protected]'>,
<re.Match?object; span=(107,?126), match='[email protected]'>,
<re.Match?object; span=(134,?161), match='[email protected]'>]在對(duì)象 obj 上還可使用?sub?方法,結(jié)果如下:
print( obj.sub('[email protected]', email), '\n' )Shengyuan Personal: [email protected]
Shengyuan Work: [email protected]
Shengyuan School: [email protected]
Obama: [email protected]在對(duì)象 obj?上還可使用?split?方法,即把 @ 前后的子串拆分出來,結(jié)果如下:
for addr in obj.findall(email):print( re.split(r'@', addr))
['quantsteven', 'gmail.com']
['shengyuan', 'octagon-advisors.com']
['g0700508', 'nus.edu.sg']
['barack.obama', 'whitehouse.gov']我們還可以再創(chuàng)建個(gè) RE 對(duì)象 obj1,專門用來做拆分。
obj1 = re.compile(r'@')for addr in obj.findall(email):print( obj1.split(addr))
['quantsteven', 'gmail.com']
['shengyuan', 'octagon-advisors.com']
['g0700508', 'nus.edu.sg']
['barack.obama', 'whitehouse.gov']3.1
密碼例子
密碼通常有如下要求。
最少 8 個(gè)最多 16 個(gè)字符.
至少含有一個(gè)大寫字母,一個(gè)小寫字母,一個(gè)數(shù)字
至少含有一個(gè)特殊字符 @ $ ! % * ? & _,但不包括空格
根據(jù)上面要求創(chuàng)建模式,相信都可以讀懂了吧。
pat = r'^[0-9a-zA-Z@!$#%_-]{8,16}$'print( look_for(pat, 'stevenwang') )print( look_for(pat, '19831031') )print( look_for(pat, 'steven1031') )print( look_for(pat, 'steven@1031') )print( look_for(pat, 'Steven@1031') )print( look_for(pat, 's1031') )print( look_for(pat, 's@1031') )print( look_for(pat, 'stevenwang19831031') )print( look_for(pat, 'stevenwang@19831031') )
['stevenwang']
['19831031']
['steven1031']
['steven@1031']
['Steven@1031']
沒有找到
沒有找到
沒有找到
沒有找到結(jié)果好像不太對(duì),因?yàn)槊艽a必須要含有數(shù)字,大小寫和特殊字符。
這時(shí)候需要用 (?=...) 這個(gè)騷操作,意思就是匹配 ’…’ 之前的字符串。在本例中 '...' 包括小寫 [a-z],大寫 [A-Z],數(shù)字 \d,特殊字符 [@$!%*?&_],言下之義就是上面這些必須包含中密碼中。
pat = r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$@$!%*?&_])[A-Za-z\d$@$!%*?&_]{8,16}$'print( look_for(pat, 'stevenwang') )print( look_for(pat, '19831031') )print( look_for(pat, 'steven1031') )print( look_for(pat, 'steven@1031') )print( look_for(pat, 'Steven@1031') )print( look_for(pat, 's1031') )print( look_for(pat, 's@1031') )print( look_for(pat, 'stevenwang19831031') )print( look_for(pat, 'stevenwang@19831031') )
沒有找到
沒有找到
沒有找到
沒有找到
['Steven@1031']
沒有找到
沒有找到
沒有找到
沒有找到結(jié)果完全正確。
上面模式的可視圖如下:

3.2
郵箱例子
首先定義郵箱地址的模式 '\S+@\S+',還記得 \S 是非空格字符,基本代表了所需的字符要求。我們想從從 email.txt 文本中篩選出所有郵箱信息。
pat = r'\S+@\S+'obj = re.compile(pat)email_list = []hand = open('email.txt')for line in hand:line = line.rstrip()email_addr = obj.findall(line)if len(email_addr) > 0:email_list.append(email_addr[0])list(set(email_list))

咋一看結(jié)果是對(duì)的,但細(xì)看(高亮處)有些郵箱地址包含了 <> 的符號(hào),或者根本不是正常的郵箱地址,比如 apache@localhost。
這時(shí)候我們需要在模式中添加更多規(guī)則,如下。
'[a-zA-Z\d]\S+?代表第一字符要是數(shù)字或字母
\w+\.[a-z]{2,3} 代表 A.B?這樣的結(jié)構(gòu),其中 A 由若干字母數(shù)字下劃線組成,而 B 由 2 或 3 個(gè)小寫字母組成(因?yàn)橥ǔ`]箱最后就是 com, net, gov, edu 等等)。
pat = r'[a-zA-Z\d]\S+@\w+\.[a-z]{2,3}'
結(jié)果正常。
3.3
摘要例子
在下面摘要中獲取人物、買賣動(dòng)作、股票數(shù)量、股票代號(hào)、日期和股價(jià)這些關(guān)鍵信息。
news?=?\"""Jack Black sold 15,000 shares in AMZN on 2019-03-06 at a price of $1044.00.David V.Love bought 811 shares in TLSA on 2020-01-19 at a price of $868.75.Steven exercised 262 shares in AAPL on 2020-02-04 at a price of $301.00."""
給大家留個(gè)任務(wù),讀懂下面代碼,看懂了本帖知識(shí)就掌握了。我相信能看到這里的都可以看懂。
pat = r'([a-zA-Z. ]*)' \'\s(sold|bought|exercised)' \'\s*([\d,]+)' \'.*in\s([A-Z]{,5})' \'.*(\d{4}-\d{2}-\d{2})' \'.*price of\s(\$\d*.\d*)'re.findall( pat, news )
[('Jack Black', 'sold', '15,000', 'AMZN', '2019-03-06', '$1044.00'),
?('David V.Love', 'bought', '811', 'TLSA', '2020-01-19', '$868.75'),
?('Steven', 'exercised', '262', 'AAPL', '2020-02-04', '$301.00')]上面模式的可視圖如下:

累死了,這次真不想總結(jié)了。
記住元字符的用處:集合、次數(shù)、并列、獲取和轉(zhuǎn)義。
記住函數(shù)的用法:先 compile 成 RE 對(duì)象,在 findall 或 finditer,由你想查看的信息完整度決定。
下帖我會(huì)來個(gè)關(guān)于 RE 的實(shí)際案例分析,并記錄我在操作時(shí)遇到的問題和解決方案。
Stay Tuned!

