「Python ?正則」使用專題總結(jié)

我的施工之路
4列表專題
今天開(kāi)始Python進(jìn)階模塊總結(jié)之正則專題,目錄結(jié)構(gòu)如下:
1 學(xué)習(xí)正則的價(jià)值
2 正則學(xué)習(xí)前的幾個(gè)準(zhǔn)備
Q1 字符 `r`是干啥的?
Q2 什么是一個(gè)原子操作?
Q3 怎么理解正則中的轉(zhuǎn)義?
3 掌握最常用規(guī)則
情況1:最普通查找
情況2:使用通用字符
情況3:使用元字符
4 有個(gè)棘手的場(chǎng)景
5 學(xué)會(huì)提取子串的技能
6 使用捕獲的注意事項(xiàng)
1 學(xué)習(xí)正則的價(jià)值
正則應(yīng)用廣泛。不僅在Python語(yǔ)言中使用,其他語(yǔ)言也都在用,并且不同語(yǔ)言間的正則語(yǔ)法極為相似。同時(shí)主流操作系統(tǒng),尤其linux系統(tǒng)的命令窗口中,也會(huì)經(jīng)常使用到正則。還有,Python的常用包如Pandas,也經(jīng)常遇到正則。
不僅使用廣泛,正則功能也很強(qiáng)大,還有書寫簡(jiǎn)便,因此這項(xiàng)技能值得我們仔細(xì)研究和掌握。
2 正則學(xué)習(xí)前的幾個(gè)準(zhǔn)備
Q1 字符 r是干啥的?
經(jīng)常見(jiàn)過(guò)正則表達(dá)式前有一個(gè)字符 r,它的作用是告訴解釋器后面的一串是原生字符串,按照字面意思解釋即可。如:
s1?=?r'\n.*'
print(s1)?
它告訴編譯器s串第一個(gè)字符是\,第二個(gè)字符是n.打印的結(jié)果就是它本身:
\n.*
而如果不帶前綴字符r,即:
s2?=?'\n.*'
print(s2)
解釋器認(rèn)為前兩個(gè)字符\n為轉(zhuǎn)義字符,一個(gè)新行的意思,打印結(jié)果為一個(gè)換行加.*,如下所示:
.*
Q2 什么是一個(gè)原子操作?
微觀世界中,如果定義原子是組成事物的最基本單元,那么就可理解為原子不能再分了。同理此處,正則的原子操作是指不能再被分割的正則表達(dá)式操作。
如正則中的+指前面的一個(gè)原子操作出現(xiàn)至少1次。例如:66+表示第一個(gè)字符為6,第二個(gè)字符6和第三個(gè)字符+聯(lián)合起來(lái)表示至少出現(xiàn)1次字符6,因此綜合起來(lái)至少要有2個(gè)6緊鄰的串才能滿足此正則表達(dá)式(下面會(huì)詳細(xì)講到)。
\w+表示字母數(shù)字下劃線中的任意一個(gè)字符(\w指代的)至少出現(xiàn)1次,那么\w就是一個(gè)原子操作。
因此,普通字符是原子,正則中的通用字符(下面會(huì)講到)也是原子。大家記住原子這個(gè)概念。
Q3 怎么理解正則中的轉(zhuǎn)義?
正則世界中,重新定義了幾個(gè)新的轉(zhuǎn)義字符。
一個(gè)轉(zhuǎn)義字符\+一個(gè)字符,轉(zhuǎn)義后會(huì)改變?cè)址囊饬x,它不再是它,而是賦予一個(gè)新的含義。
例如,w本身就是一個(gè)英文字符w,沒(méi)有其他任何含義。但是,前面加一個(gè)轉(zhuǎn)義字符 \ 后,含義發(fā)生重大改變,w它不再是w,而是\要與w連在一起,被解釋器解釋為匹配以下字符集合中的任意一個(gè):
pat?=?'\w'
等于:
pat?=?'[0123456789
??????AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz
??????_]'
即匹配數(shù)字、大小寫字母和下劃線_字符集合中的任意一個(gè)。
你看,一個(gè)通用轉(zhuǎn)義字符\w直接就指代上面這一大串,寫法多么簡(jiǎn)便,同時(shí)在正則的世界里又經(jīng)常被用到,故被稱為:通用正則字符
類似的通用正則字符還有幾個(gè),下面也會(huì)講到。做一件事前,把規(guī)則弄清,觸類旁通,相信大家理解其他幾個(gè)也沒(méi)問(wèn)題。
3 掌握最常用規(guī)則
為了更清晰的展示,咱們只涉及最常用的規(guī)則,一來(lái)縮短篇幅,二來(lái)降低大家學(xué)習(xí)曲線,三來(lái)一類問(wèn)題掌握一個(gè),觸類旁通即可。
情況1:最普通查找
最普通查找就是需要找啥就寫啥,沒(méi)有使用正則的規(guī)則。如下是關(guān)于小說(shuō)《燦爛千陽(yáng)》中的一段話,從中找出單詞friendship,可能出現(xiàn)多次:
s?=?"""
#?Mariam?is?only?fifteen?
#?when?she?is?sent?to?Kabul?to?marry?the?troubled?and?bitter?Rasheed,
#?who?is?thirty?years?her?senior.?
#?Nearly?two?decades?later,?
#?in?a?climate?of?growing?unrest,?tragedy?strikes?fifteen-year-old?Laila,?
#?who?must?leave?her?home?and?join?Mariam's?unhappy?household.?
#?Laila?and?Mariam?are?to?find?consolation?in?each?other,?
#?their?friendship?to?grow?as?deep?as?the?bond?between?sisters,?
#?as?strong?as?the?ties?between?mother?and?daughter.?
#?With?the?passing?of?time?comes?Taliban?rule?over?Afghanistan,?
#?the?streets?of?Kabul?loud?with?the?sound?of?gunfire?and?bombs,?
#?life?a?desperate?struggle?against?starvation,?brutality?and?fear,?
#?the?women's?endurance?tested?beyond?their?worst?imaginings.?
#?Yet?love?can?move?a?person?to?act?in?unexpected?ways,?
#?lead?them?to?overcome?the?most?daunting?obstacles?with?a?startling?heroism.?
#?In?the?end?it?is?love?that?triumphs?over?death?and?destruction.?
#?A?Thousand?Splendid?Suns?is?an?unforgettable?portrait?of?a?wounded?country?and
#??a?deeply?moving?story?of?family?and?friendship.?
#??It?is?a?beautiful,?heart-wrenching?story?of?an?unforgiving?time,?
#??an?unlikely?bond?and?an?indestructible?love.
"""
使用正則前,先導(dǎo)入re模塊,再定義正則表達(dá)式,然后使用findall方法找出所有匹配
import?re
pat?=?'friendship'
result?=?re.findall(pat,s)
print(result)?
#?共找到兩處:
#?['friendship',?'friendship']
以上就是使用正則的最普通例子。如果要找出前綴為grow的單詞,比如可能為grows, growing 等,最普通查找實(shí)現(xiàn)起來(lái)就不方便。
然而,借助于下面介紹的元字符、通用字符和捕獲組合起來(lái),便能應(yīng)對(duì)解決復(fù)雜的匹配查找問(wèn)題。
情況2:使用通用字符
在正則的世界里,通用字符指幫助我們更加簡(jiǎn)便的寫出匹配規(guī)則的字符。
如上面文字,下面正則匹配串能找出以d開(kāi)始,[a-z]表示的任意一個(gè)小寫英文字符,{7}表示小寫英文字符出現(xiàn)7次(下面情況3會(huì)說(shuō)到),也就是匹配出來(lái)的子串長(zhǎng)度為1+7=8:
pat?=?'d[a-z]{7}'
result?=?re.findall(pat,s)
匹配結(jié)果為:
['daughter',?'desperat',?'daunting',?'destruct',?'destruct']
同理,模式串pat = 'd[a-z]{10}'匹配的結(jié)果為:
['destruction',?'destructibl']
模式串pat = 'd[a-z]{11}'匹配的結(jié)果為:
[?'destructible']
你看,通用字符[a-z]使用真方便,5個(gè)字符一下就表達(dá)了所有26個(gè)小寫的字符,但是注意[a-z]匹配26個(gè)小寫字符的任意一個(gè).
類似功能的通用字符還包括:
[A-Z]??匹配大寫英文字母
[0-9]??匹配一個(gè)0-9之間的數(shù)字
還有更加強(qiáng)大的通用字符:
\s??匹配空白字符,如\n?\t?\b等
\w??匹配任意字母、數(shù)字、下劃線?
\d??匹配十進(jìn)制數(shù)字0-9
而\S, \W, \D 分別對(duì)應(yīng) \s, \w, \d匹配字符集的補(bǔ)集,例如\S 的意思是匹配 \s 以外的其他任意字符。
情況3:使用元字符
元的含義大家不妨理解為用來(lái)描述它后面事物的類,如元類用來(lái)創(chuàng)建描述類的類,元模型描述一個(gè)模型的模型,因此推而廣之,元字符用來(lái)描述字符的字符。
理解以上后,你再看正則中使用最普遍的一個(gè)元字符 +,它是用來(lái)描述前面一個(gè)原子出現(xiàn)次數(shù)的字符,表示前一個(gè)原子出現(xiàn)1次或多次都可。
例如,在尋找手機(jī)靚號(hào)時(shí),正則表達(dá)式66+,表示前一個(gè)原子6至少出現(xiàn)1次,因此連上第一個(gè)6,表示電話號(hào)碼中至少有兩個(gè)66緊鄰。因此,電話號(hào)碼18612652166、17566665656都滿足要求,而號(hào)碼18616161616不符合要求。
類似功能的元字符,還包括如下。功能相似,不再贅述:
*?前面的原子重復(fù)0次、1次、多次?
??前面的原子重復(fù)0次或者1次?
+?前面的原子重復(fù)1次或多次
{n}?前面的原子出現(xiàn)了?n?次
{n,}?前面的原子至少出現(xiàn)?n?次
{n,m}?前面的原子出現(xiàn)次數(shù)介于?n-m?之間
4 有個(gè)棘手的場(chǎng)景
了解以上規(guī)則后,我們能去完成很多匹配任務(wù),比如一些字符串匹配任務(wù);查找某個(gè)手機(jī)號(hào)是不是靚號(hào);找到文字中出現(xiàn)某個(gè)模式的所有地方。
但是,使用正則還會(huì)經(jīng)常遇到的一大場(chǎng)景。試想,從一個(gè)文件里提取出所有如下格式的鏈接,并全部導(dǎo)出來(lái):
https://github.com/jackzhenguo/python-small-examples
https://gitbook.cn/gitchat/column/5e37978dec8d9033cf916b5d
截止目前,我們還不能完成這樣的匹配任務(wù)。鑒于這種匹配任務(wù)確實(shí)很常見(jiàn),因此你很有必要學(xué)會(huì)下面這項(xiàng)技能。
5 學(xué)會(huì)提取子串的技能
今天以我寫過(guò)的《Python 60天》專欄中的一段文字,提取出里面的鏈接為例,闡述提取子串的實(shí)用性。
先貼上文字(有刪減改動(dòng)),將這段文字賦值給變量 urls:
urls?=?"""
基于 Python 的包更是枝繁葉茂,遍地開(kāi)花,“Tiobe 編程語(yǔ)言排行榜”最新統(tǒng)計(jì)顯示 Python 是增長(zhǎng)最快的語(yǔ)言。

接下來(lái),與大家,還有遠(yuǎn)在美國(guó)做 AI 博士后研究的 Alicia,一起開(kāi)始我們的 60?天 Python 探索之旅吧。
所有的這些考慮,都是為了讓大家在短時(shí)間內(nèi)掌握 Python 技術(shù)棧,多一個(gè)生存的本領(lǐng)。拿到理想的 Offer 后,早日過(guò)上自己想要的生活。
讓我們開(kāi)始吧。
如下,按照是否為靜態(tài)/動(dòng)態(tài)語(yǔ)言,弱類型/強(qiáng)類型兩個(gè)維度,
總結(jié)常用的語(yǔ)言分類。
?###?四大基本語(yǔ)法
"""
你可能很快寫出如下的正則表達(dá)式:
#?元字符.表示匹配除\n字符外的任意一個(gè)字符
#?元字符*表示匹配前面一個(gè)原子0次或多次
pat?=?r'https:.*'?
然后導(dǎo)入re模塊,使用findall方法找出所有匹配:
import?re
result?=?re.findall(pat,urls)
print(result)
運(yùn)行結(jié)果顯示如下,觀察發(fā)現(xiàn)2個(gè)匹配,但是每個(gè)匹配鏈接都包括冗余字符,因此匹配錯(cuò)誤:
['https://images.gitbook.cn
/2020-02-05-014719.png)',
'https://images.gitbook.cn
/2020-02-05-080211.png)?###?四大基本語(yǔ)法']
我們?cè)偕晕?yōu)化原正則表達(dá)式為:
#?添加?\)?表示待匹配子串以右括號(hào)結(jié)尾
pat?=?r'https:.*\)'
打印結(jié)果顯示如下,結(jié)果確實(shí)好一點(diǎn),但是依然包括右括號(hào),結(jié)果還是錯(cuò)誤的:
['https://images.gitbook.cn/
2020-02-05-014719.png)',?
'https://images.gitbook.cn/
2020-02-05-080211.png)']
所以掌握提取子串的技能就很重要,實(shí)現(xiàn)提取子串也很簡(jiǎn)單,只需把想要返回的子串加上一對(duì)括號(hào)就行,如下所示:
#?把想要返回的子串外面添加一對(duì)括號(hào)
pat?=?r'(https:.*)\)'
此時(shí)返回結(jié)果完全正確,無(wú)任何多余字符。想要返回的子串外面添加一對(duì)括號(hào)還有個(gè)專業(yè)叫法:捕獲或分組。
6 使用捕獲的注意事項(xiàng)
捕獲功能非常實(shí)用,使用它需要區(qū)分一點(diǎn),貪婪捕獲和非貪婪捕獲。前者指在滿足匹配模式前提下,返回包括盡可能多的字符匹配模式;后者指滿足匹配條件下,盡可能少的捕獲。
我們偽造一個(gè)理想狀況下的案例:
htmlContent?=?"""
????????<div><div><h2>這是二級(jí)標(biāo)題h2>div><div><p>?這是一個(gè)段落>/p>div>div>
"""
貪心捕獲使用(.*),如下所示:
pat?=?r"(.*)"
result?=?re.findall(pat,htmlContent)
結(jié)果為如下,盡可能長(zhǎng)的捕獲,而不是遇到第一個(gè)
['<div><h2>這是二級(jí)標(biāo)題h2>div><div><p>?這是一個(gè)段落>/p>div>']
而非貪心捕獲的正則表達(dá)式為,如下:
pat?=?r"(.*?)"
result?=?re.findall(pat,htmlContent)
print(result)
結(jié)果為兩個(gè)元素,遇到第一個(gè)
['<div><h2>這是二級(jí)標(biāo)題h2>',?
??'<p>?這是一個(gè)段落>/p>']
以上例子僅僅用作演示兩者區(qū)別,實(shí)際的html結(jié)構(gòu)含有換行符等,環(huán)境比上面要復(fù)雜的多,貪心和非貪心捕獲的寫法可能不會(huì)導(dǎo)致結(jié)果不同,但是我們依然需要理解它們的區(qū)別。
以上就是正則使用需要掌握的主要知識(shí),整篇文章的結(jié)構(gòu)總結(jié)如下:
1 學(xué)習(xí)正則的價(jià)值
2 正則學(xué)習(xí)前的幾個(gè)準(zhǔn)備
Q1 字符 `r`是干啥的?
Q2 什么是一個(gè)原子操作?
Q3 怎么理解正則中的轉(zhuǎn)義?
3 掌握最常用規(guī)則
情況1:最普通查找
情況2:使用通用字符
情況3:使用元字符
4 有個(gè)棘手的場(chǎng)景
5 學(xué)會(huì)提取子串的技能
6 使用捕獲的注意事項(xiàng)
掌握以上這些正則知識(shí)后,相信就能明白主要的正則原理了。具體到有些細(xì)節(jié)時(shí)大家可參考外部的正則可視化工具和檢驗(yàn)工具,來(lái)驗(yàn)證一下自己寫的正則表達(dá)式就行。
【文末送書】贈(zèng)送北大出版社的暢銷書籍《Python數(shù)據(jù)分析與大數(shù)據(jù)處理》4?本,此書內(nèi)容充實(shí)、富有體系,點(diǎn)擊下面圖片了解詳情。送書還是按照從留言中選擇的常規(guī)方法。

