Python正規表示式:不一定要會,但會了超省力
文章推薦指數: 80 %
建立Regex 物件
Skiptocontent
最後更新時間:2021年9月1日
「正規表示式」(Regualrexpression)看了就讓人頭暈,一堆難懂的符號與文字。
尤其,正規這兩個字讓人覺得莫名恐懼。
如果你覺得「正規表示式」的名稱讓人摸不著頭緒,你可以試著從英文來理解,然後在心中為它取一個你比較喜歡的中文名稱,也許就不會那麼排斥了。
「正規表示式」是Regualrexpression的中文翻譯,由兩個英文單字組成,regular及expression。
regular是規則的意思,expression則是表達的意思,中文翻譯把Regualrexpression翻譯成「正規表示式」,代表一種有規則、有規律的表達方式。
也有人把Regularexpression翻譯為正則表達式、規則運算式、正規表式法。
其實你也可以不必去記這些中文名稱,知道Regularexpression就是指,有規則的表達方式就可以了,重要的是讓它的功能幫助到你。
接下來我們會介紹正規表示式的功用,比較使用及不使用正規表示式的差異,這個比較文有些冗長,你可以使用快速閱讀,直接跳到你需要的操作說明段落。
快速閱讀
Python正規表示式的功用學會正規表示式的好處不使用正規表示式使用正規表示式開始編寫正規表示式最重要的第一步:匯入re模組建立Regex物件正規表示式入門操作比對Regex物件使用search()方法使用findall()方法分組後,比對結果的差異分組後使用search()方法,可指定要返回的值分組後使用findall()方法,返回值為內含tuple的串列各種幫助你比對的工具常見的字元分類具有特殊功能的符號compile()函式第二個引數使用sub()方法取代指定的字串第一步 找出目標第二步 將規則分組書籍及課程資訊
Python正規表示式的功用
面對「正規表示式」你可能還會有這些問題:
一定要學會「正規表示式」嗎?「正規表示式」可以幹嘛?「正規表示式」功能是什麼?「正規表示式」是不是很難?不會「正規表示式」就不能學會寫程式嗎?
首先,你不一定要會「正規表示式」也是可以寫程式。
學會什麼都是你的選擇,你可以不會「正規表示式」,依然可以編寫Python程式碼,讓程式運作;就好像你可以不會開車、也不會搭乘交通工具,就是只會走路,你還是可以從台北移動到高雄,只是會比較費力、費時。
「Python正規表示式」可以做什麼?
關於這個問題,讓我們回到學程式的目的。
whyyoulearncoding
最大的目,是要當個收入高、有技術涵養的工程師?跟上趨勢,擁有不易被社會淘汰的專長?
這可能是許多人學程式語言的目的。
不過就本質上而言,編寫程式語言的目的,是要利用程式幫助我們處理事情,將繁瑣複雜的事簡單化,讓規律的事情自動化。
而「正規表示式」的功用就是:幫你讓複雜的程式碼規律化、簡單化。
學會正規表示式的好處
《Python自動化的樂趣》這本書將「正規表示式」講解的很淺顯易懂,作者AISweigart這麼形容,’會’與’不會’「正規表示式」的差異:
會用「正規表示式」(Regualrexpression)意味著可以用3個步驟就解決問題,而不需要耗費3,000步才能搞定。
好處總是比較出來才會知道,我們來看看《Python自動化的樂趣》這本書,所舉的例子,讓你評估一下是否有必要學會「正規表示式」。
python自動化的樂趣中文版-2
情境是這樣的:
設計一個可以辨別電話號碼的程式,讓你可以在一大堆的資料中,用程式快速幫你抓出電話號碼。
要查找的電話號碼格式:
02-1111-2222,區碼二碼數字–四碼數字–四碼數字。
不使用正規表示式
不使用「Python正規表示式」的情況下,我們設計一個名稱為isPhoneNumber()的函式。
首先檢查長度是不是12個位元,再依序檢查每個位置是數字或是–符號,每一個條件都必需成立,才會判斷為電話號碼。
函式內,寫滿了if陳述式與for迴圈,一個字元一個字元的檢查,資料是不是數字及–符號,全部符合的情況才會返回True,True代表某段文字是電話號碼,False代表不是電話號碼。
最後面的四行print()程式碼,執行電話號碼的辨別工作。
>>>defisPhoneNumber(text):
>>>iflen(text)!=12:
>>>returnFalse
>>>foriinrange(0,2):
>>>ifnottext[i].isdecimal():
>>>returnFalse
>>>iftext[2]!='-':
>>>returnFalse
>>>foriinrange(4,7):
>>>ifnottext[i].isdecimal():
>>>returnFalse
>>>iftext[7]!='-':
>>>returnFalse
>>>foriinrange(8,11):
>>>ifnottext[i].isdecimal():
>>>returnFalse
>>>returnTrue
>>>
>>>print('thisisanumber:02-1234-5555')
>>>print(isPhoneNumber('02-1234-5555'))
>>>print('thisisanumber:ohno!regularexpression')
>>>print(isPhoneNumber('ohno!regularexpression'))
執行程式後,得到的結果
thisisanumber:02-1234-5555
True
thisisanumber:ohno!regularexpression
False
成功辨識出02-1234-5555是電話號碼,而ohno!regularexpression不是電話號碼。
接著我們再將最後四行的print()程式碼調整一下,搭配for迴圈與if陳述式,減少你打一大堆print(),調整後如下:
>>>Data='PleasecallDavidat02-8888-1688bytoday.02-9888-9898ishisofficenumber.'
>>>foriinrange(len(Data)):
>>>number=Data[i:i+12]
>>>ifisPhoneNumber(number):
>>>print('PhoneNumber:'+number)
>>>print('Pleasecallhim/herassoonaspossible.')
執行結果如下方所示:
PhoneNumber:02-8888-1688
PhoneNumber:02-9888-9898
Pleasecallhim/herassoonaspossible.
這是不使用正規表示式,來找尋、比對資料的方法,你需要幫每個字元設定條件,以判別是不是電話號碼的格式。
使用正規表示式
而如果使用「Python正規表示式」來檢查,會是什麼狀況呢?
「正規表示式」有強大的比對功能,為多種字元條件設定了特定的符號,例如要找數字,以\d表示,就會讓Python去判別字元是不是數字。
一樣是針對找電話號碼情境,使用「正規表示式」的程式碼會是這樣:
>>>importre
>>>phoneNumRegex=re.compile(r'\d\d-\d\d\d\d-\d\d\d\d')
>>>result=phoneNumRegex.findall('PleasecallDavidat02-8888-1688bytoday.02-9888-9898ishisofficenumber.')
>>>number=','.join(result)
>>>print('PhoneNumber:'+number)
>>>print('Pleasecallhim/herassoonaspossible.')
沒錯,如果你會使用「正規表示式」,就可以很簡潔的你做一個功能一樣的小程式。
執行結果:
PhoneNumber:02-8888-1688,02-9888-9898
Pleasecallhim/herassoonaspossible.
程式碼減短不少,並且也可以順利找到文字中的電話號碼。
除此以外,調整規則也會方便很多。
兩種方法都可以在辨別出字串的電話號碼,使用正規表示式方便也簡短很多。
而且你也不用在各個if、for之間數著你的range()內的引數應該設多少,第幾個位置該是數字、第幾個位置該是–符號。
前面的例子中,不使用正規表示式的程式碼需要23行,使用正規表示式則只要6行(本文例子的字串,因螢幕寬度限制,變為多行的仍以單行計算)。
使用正規表示式中的compile()函式、findall()方法、\d,取代一大堆if、for,的確省了不少力氣。
這樣比較下來,有激起你想學習正規表示式的動力嗎?
如果有,就接著往下看吧。
開始編寫正規表示式
前面找電話號碼的例子,印證使用「正規表示式」讓我們的程式碼簡潔許多,也免去了思考一大堆if陳述句與for 迴圈,要怎麼使用的恰到好處的燒腦狀況。
該怎麼在Python中使用「正規表示式」呢?
最重要的第一步:匯入re模組
因為「正規表示式」沒有內建在Python中,所以使用前,必須在程式碼的最開頭匯入re模組。
怎麼匯入?就是打上:
>>>importre
import就是匯入的意思,只要你要匯入任何模組,都是用import並在其後加上模組名稱。
re是regularexpression模組的名稱。
匯入re模組,絕對是你要開始編寫「正規表示式」的第一步,沒有匯入re模組,你寫得再好的「正規表示式」也不會執行,只會得到錯誤。
NameError:name're'isnotdefined
當你看到這個錯誤訊息,就是你忘記匯入re模組了,快在程式碼開頭加上importre吧!
建立Regex物件
匯入re 模組就像是找到一部車子,你還要學會怎麼發動引擎才能開始讓車子運轉,送你到達目的地。
要啟動引擎,要先找到鑰匙孔或者是啟動的按鍵。
建立Regex物件就是啟動的關鍵。
Regex就是Regularexpression的縮寫,所以Regex物件就是指,正規表示式物件。
要建立Regex物件,要使用re模組中的compile()函式,以re.compile()來表示。
compile是編譯的意思,在compiel()函式的括號中填入你需要的規則,也就是編譯一個規則,放到括號中。
以找電話號碼為例,我們設定要找的號碼格式是:02-1111-2222,區碼二碼數字-四碼數字–四碼數字。
在正規表示式中,\d代表一個只能是數字的字元,我們就用\d來建立電話號碼格式。
>>>phoneNumRegex=re.compile(r'\d\d-\d\d\d\d-\d\d\d\d')
建立的好的Regex物件,我們把它指派給一個好懂得變數,方便之後使用,才不用一直打re.compile(r’\d\d-\d\d\d\d-\d\d\d\d’),只要打出phoneNumRegex,就代表你所設定的規則。
眼尖的你可能會發現,compile()括號內怎麼有個r,要求原始字串。
這是因為正規表示式,會用到許多符號,可能會與Python內建的功能相衝突,所以會在你需要的格式前面加上r,代表原始字串raw。
關於原始字串,你可以在《Python字串(string)基礎與20種常見操作》這篇文章的「顯示原始字串」段落,喚起你對原始字串的印象。
正規表示式入門操作
「正規表示式」有很多種形態的規則,不過我們先學幾個基本的用法就好,等到對正規表示式的觀念建立了,再去拓展、延伸。
不需要一開始就給自己過多的資訊量,被博大精深的正規表示式嚇跑了,總是要先有入門,建立基礎後,再藉著基礎知識去鑽研更深一層的知識。
就像是學開車,如果教練第一天就教你全部的操作方法,但你連發動引擎都不會,一定會覺得開車超難的,而且還要求你瞭解汽車三萬多個零件的名稱、用途、功能、故障排除,以及所有的交通法規,你大概會覺得這輩子是不可能學會開車了。
資訊過多無法消化
現在,我們就先來學習「正規表示式」的入門操作吧!
比對Regex物件
建立好Regex物件後,也就建立好你要比對資料的規則了。
比對資料可以使用search()或findall()方法,search()只會找出符合規則的一筆資料,findall()則是會把所有符合的資料都找出來給你,所以才叫findall。
這兩個方法找東西的成果不一樣,操作上也有點不同。
使用search()方法
search()方法要結合Regex物件使用,下方的例子中,phoneNumRegex是我們為了找電話號碼建立的物件名稱。
在phoneNumRegex後方加個句點,輸入search(),並在括號內填入你要比對、查找的資料。
如果有找到符合規則的,便會得到一個Match物件;如果沒有找到,則會得到None。
match的英文意思是吻合的意思。
通常我們會將Match物件指派給mo這個變數,mo是matchobject的簡稱,跟momo購物沒有關係。
>>>phoneNumRegex=re.compile(r'\d\d-\d\d\d\d-\d\d\d\d')
>>>mo=phoneNumRegex.search('Callmeat02-8888-1688bytoday.')
>>>print(mo.group())
而呼叫Match物件需要使用group()方法,才可以把找到的東西呈現出來,也就是程式才會顯示出比對正確的字串。
執行程式後,得到下方的結果:
02-8888-1688
search()方法可以幫助我們找對比對成功的第一筆電話,所以如果你都只要找出一筆資料,你已經學會如何讓正規表示式為你所用了。
如果你要找到所有的電話,或是所有符合規則的資料,那你可以再學一個方法,findall()方法。
使用findall()方法
findall()方法一看就是會幫你把所有符合規則的資料找齊,findall不就說明了一切。
findall()方法的操作與search()方法相似,也是在Regex物件後方加個句號,放上findall(),並在括號內填入你要比對的目標資料。
下方是我們前面提過的例子,把比對出來的資料都指派給result這個變數。
>>>phoneNumRegex=re.compile(r'\d\d-\d\d\d\d-\d\d\d\d')
>>>mo=phoneNumRegex.findall('PleasecallDavidat02-8888-1688bytoday.02-9888-9898ishisofficenumber.')
>>>print(mo)
程式執行結果:
['02-8888-1688','02-9888-9898']
findall()與search()不同的是:
你不需要使用group()就可以呈現出比對成功的結果。
findall()方法返回串列(list),串列內的項目是字串(string);然而search()方法擇返回字串(string)。
也就是說兩個方法所得到的產出,資料型態是不同的,前者是串列(list),後者是字串(string)。
資料型態不同,就會影響你運用資料的方式!
分組後,比對結果的差異
前面的例子,我們沒有將re.compile()函式中的規則分組。
這裡的分組是指幫規則分區塊,用小括號來區分。
將re.compile()函式中的規則分組後,search() 及findall()返回的結果,會與沒有分組時有些差異。
下方的例子,我們將電話號碼分組,區碼二碼數字-四碼數字–四碼數字,分出了三組。
>>>phoneNumRegex=re.compile(r'(\d\d)-(\d\d\d\d)-(\d\d\d\d)')
分組後使用search()方法,可指定要返回的值
規則進行分組後,你可以在group()的括號內填入組別,來取得指定的比對資料。
下方例子,我們把規則分成三組(\d\d)-(\d\d\d\d)-(\d\d\d\d),一個括號代表一組。
>>>phoneNumRegex=re.compile(r'(\d\d)-(\d\d\d\d)-(\d\d\d\d)')
>>>mo=phoneNumRegex.search('Pleasecallmeat02-8888-1688or02-3333-2323bytoday.')
>>>print(mo.group(3))
>>>print(mo.group(0))
這裡要注意的是,group(0)或group()代表全部,group(1)代表第一組,以此類推,這裡的排序方式又跟index要從0開始算起有點不一樣,需要留意一下。
執行程式後,你會得到這樣的結果:
1688
02-8888-1688
將規則以括號分組後,你可以更精準地取用比對出來的資料,例如你找出了1000筆電話,但你只需要區碼,便可以用group(1),只顯示第一組的資料,也就是區碼。
分組後使用findall()方法,返回值為內含tuple的串列
同樣的,我們把規則分成三組,來看看findall()返回的值是什麼。
>>>phoneNumRegex=re.compile(r'(\d\d)-(\d\d\d\d)-(\d\d\d\d)')
>>>result=phoneNumRegex.findall('PleasecallDavidat02-8888-1688bytoday.02-9888-9898ishisofficenumber.')
>>>print(result)
執行程式碼的結果:
[('02','8888','1688'),('02','9888','9898')]
結果還是會產生串列(list),但串列(list)的第一層變成tuple,tuple內才是字串(string)。
如果你希望比對的成果可以有完整電話號碼,你可以在規則的最外層再加上括號。
>>>phoneNumRegex=re.compile(r'((\d\d)-(\d\d\d\d)-(\d\d\d\d))')
>>>result=phoneNumRegex.findall('PleasecallDavidat02-8888-1688bytoday.02-9888-9898ishisofficenumber.')
>>>print(result)
最外層的括號會被當成第一組,結果就像這樣:
[('02-8888-1688','02','8888','1688'),('02-9888-9898','02','9888','9898')]
需要特別注意的,當findall()返回的返回的串列,第一層資料變成tuple後,需要留意資料的操作上侷限,因為tuple操作較少,例如字串方法join()就不能用了。
不過你還是可以直接把值指派給不同變數,來呈現出你要的資料型態,再進行操作。
例如,你可以不在最外圍使用一個括號,而以另一種方法來獲得完整的電話號碼。
這個方法將findall() 方法回傳的結果,再進行操作,取出各個數字,用 + 運算子串成字串。
>>>phoneNumRegex=re.compile(r'(\d\d)-(\d\d\d\d)-(\d\d\d\d)')
>>>result=phoneNumRegex.findall('PleasecallDavidat02-8888-1688bytoday.02-9888-9898ishisofficenumber.')
>>>foriinrange(len(result)):
>>>area,first,second=result[i][0],result[i][1],result[i][2]
>>>print(area+'-'+first+'-'+second)
執行程式你可得到這樣的結果:
02-8888-1688
02-9888-9898
鄭重恭喜你,學會「正規表示式」了!
基本上,只要你會匯入re模組,利用compile()函式建立Regex物件,使用search()及group()或findall()方法來找到符合規則的資料,你就已經會使用「正規表示式」。
只不過因為你只會\d這個規則,所以你只會從資料中找出你要的數字,例如電話、價錢、郵遞區號、樂透號碼等,如果你要找的是e-mail、網址等其他格式的目標呢?
如果希望可以制定更多元的規則,你可以再多了解還有哪些規則可以讓你使用,幫助你比對資料,這樣就可以擴大你的「正規表示式」功力了。
各種幫助你比對的工具
你需要比對的資訊可能不只數字,有可能是Email、網址,或是你要把比對得到的姓名隱藏起來,以***表示。
接下來介紹常見的字元規則,讓你使用「正規表示式」時,有更多工具可以使用。
常見的字元分類
從找電話號碼的例子中,你知道\d代表任何數字字元,0-9的數字都會符合\d的代表的規則,其實還有很多類似的字元分類,常見的幾個列在下面的表格中。
分類符號規則含義符合的例子\d從0到9的數字123\w任何的字母、數字及底線符號_yes123_或YES123_\s空白字元,包括空格、定位符號空格(tab)、換行符號有點難呈現,請用規則想像一下\D\d規則以外的字元。
即除了數字以外的字元abc或ABC\W\w規則以外的字元。
即除了數字、字母、底線以外的字元,或-\S\s規則以外的字元。
即除了\d空白字元以外的字元123或yes123_或,
具有特殊功能的符號
這些符號在正規表示式中,具有特殊功能,如果只是要表示這個符號,需要加上反斜線,例如你要比對句點,要打上\.,或是使用自訂規則[\]。
符號功能.比對任何字元,但是換行符號除外。
|可以比對多個規則,第一個符合為比對結果。
?比對符合0次或1次。
或者代表節儉的比對,比對最少的就停止。
.*?比對到符合的就停止,不貪婪。
*比對符合0次或多次,或者代表貪婪的比對,盡可能比對最多。
.* 代表比對所有字元,貪婪的一直找下去。
+比對符合1次或多次。
[]自訂比對格式。
如為除外的比對,左側中括號加上^。
[^123],表示比對沒有123以外的字元。
{}指定比對的次數。
{3}代表三次,{3,5}代表三到五次,逗號前後可以擇一省略。
()將規則分組。
^比對開頭符合規則的字串。
在[]中代表除外,位置必須緊接在[之後。
$比對結尾符合規則的字串。
同時使用,^與$符號,代表模式要一模一樣。
compile()函式第二個引數
compile()函式可加入第二個引數,增加設定規則的彈性。
名稱功能re.IGNORECASE或re.I比對時忽略大小寫re.DOTALL比對時包含所有的字元,換行符號也包括re.VERBOSE在compile()中,使用#註釋功能。
多行字串中,為了對齊使用的空格,不會被列為比對的規則
需要注意的是,第二個引數只能放一個值,如果三個功能你都需要用到,可以使用|符號,將之組合起來使用。
>>>requestRegex=re.compile('luck',re.I|re.DOTALL|re.VERBOSE)
這些工具,可以幫助你建立更便捷「正規表示式」,查看表格如有不明白之處,《Python自動化的樂趣》這本書,對於相關符號的解釋與範例,可以再幫助你理解。
如果書本的閱讀讓你苦惱,你還可以透由課程教學,更快的理解Python「正規表示式」,作者AlSweigart在Udemy上的課程解說,相信會讓你學會該怎麼使用這些操作。
AlSweigart的Python課程
使用sub()方法取代指定的字串
相信你一定很常看部分隱藏的名字,例如下方的得獎名單。
本圖為移民署中獎名單
sub()方法可以幫你製作類似這樣的效果,把比對到的姓名隱藏起來,以***表示。
2個步驟教你用sub()方法辦到。
第一步 找出目標
先利用compile()設定可以找到目標的規則,並利用sub(),第一個引數指定要替代的字串,第二個引數為要進行取代的字串。
下方的例子是個得獎名單,列出了最大獎到三獎,我們要獎得獎人的名字隱藏起來,先用HIDE取代(輸入你想取代的字元,此處以HIDE舉例)。
>>>importre
>>>nameRegex=re.compile(r'prize-\w+')
>>>result=nameRegex.sub('HIDE','firstprize-Phoebe,secondprize-Vivi,thirdprize-Ming')
>>>print(result)
測試一下結果,執行程式後,得到下方的結果。
firstHIDE,secondHIDE,thirdHIDE
沒問題,名字都被取代了。
第二步 將規則分組
我們希望可以顯示得獎者名字的第一個字,其餘以***取代。
首先,我們需要為compile()裡的規則,進行分組,將要留下的字元用小括號分組。
>>>nameRegex=re.compile(r'prize-(\w)\w+')
接著在sub()中填入你要取代的模式,\1代表輸入分組的第一組,即每個得獎著名字的第一個字母,其後則以***取代。
>>>result=nameRegex.sub(r'\1***','firstprize-Phoebe,secondprize-Vivi,thirdprize-Ming')
如果你區分了很多組,\1、\2、\3分別代表第一組、第二組、第三組,以此類推來運用。
完整的程式碼會是這樣:
>>>importre
>>>nameRegex=re.compile(r'-(\w)\w*')
>>>result=nameRegex.sub(r'\1***','firstprize-Phoebe,secondprize-Vivi,thirdprize-Ming')
>>>print(result)
執行後的結果:
firstprizeP***,secondprizeV***,thirdprizeM***
書籍及課程資訊
如果你想再看多一點Python「正規表示式」的例子與實作,推薦閱讀《Python自動化的樂趣》這本書的第七章。
python自動化的樂趣中文版-2
如果你想透由課程教學,更快的理解Python「正規表示式」,作者AlSweigart在Udemy上的課程解說得相當清楚。
AlSweigart的Python課程
「正規表示式」課程在第10堂課,以7個影片,用將近2個小時的課程講解這個部分。
作者的線上課程跟書本的講解順序略有不同,影片有提到的書本都有,只是線上課程畢竟是口語的教學,相對書本好懂很多,如果理解本文或書本上有碰到難點,相信這堂課可以為你帶來很多收穫。
關於更多的Python「正規表示式」資訊,你還可以參考Python官方文件說明,鉅細靡遺將各種用法都列出來了。
Python正規表示式官方文件
延伸閱讀:
Python觀念,從=開始
Pythonif陳述句的基礎與操作
Python字串基礎與20種常見操作
Python串列(list)基礎與23個常用操作
Python字典(dictionary)基礎與16種操作
學Python可以做什麼:9個Python應用報你知
12個入門Python線上課程:讓你快速學會寫程式
文章導覽
←Previous文章Next文章→
LeaveaCommentCancelReply發佈留言必須填寫的電子郵件地址不會公開。
必填欄位標示為*Typehere..Name*
Email*
Website
在瀏覽器中儲存顯示名稱、電子郵件地址及個人網站網址,以供下次發佈留言時使用。
搜尋站內文章
Searchfor:
近期文章
Python基礎觀念教學課程,一步一步帶你理解程式碼怎麼運作
總是學不會?1堂教你如何學習的課程,讓你不再跟腦袋作對
CSS繼承(inheritance):1個可能被你忽略的重要觀念
CSSTransition屬性的4個操作
2個要點了解CSS圓角屬性border-radius操作
課程推薦
好書推薦成功,從聚焦一件事開始
架站主機推薦
延伸文章資訊
- 1Regular Expressions in Python -A Beginners Guide - Analytics Vidhya
- 2Python - Regular Expressions - Tutorialspoint
- 3Python RegEx - W3Schools
A RegEx, or Regular Expression, is a sequence of characters that forms a search pattern. RegEx ca...
- 4PYTHON regular expression 實戰 - iT 邦幫忙
regular expression簡稱re,中文:正規表示式. 首先推薦以下網頁學習: Regular Expression介紹中文網頁,而且把符號都說得很清楚!
- 5Python - Regular Expressions - Tutorialspoint
Python - Regular Expressions ... A regular expression is a special sequence of characters that he...