python3 正則表達式 一點通

一、開篇

想要學好一門技能,不僅僅要勤讀書,還要勤練習。作爲網絡工程師的我來講,爲了適應網絡變革浪潮不斷的逼自己學習python,在公司內部也不斷的推動網絡向自動化的演進車輪。前進的道路總是曲折的,最近要分析防火牆20多G的log文件。想從中提取有用信息,無奈學藝不精寫不出準確的正則表達式,不得不通過大量的if語句來判斷,即喫力不討好,又勞民傷財。故趁機系統學習一下python 正則表達式。這個東西我們經常是當工具來使用,也就是在需要的時候再去查一查,不用就拋之腦後。對於大多數人來講這樣並麼有什麼壞處,但是要想快速準確的匹配出自己想要的內容,那就得老老實實的掌握它的語法。以下進入主題部分。

二、什麼是正則表達式

此處抄用一下度孃的話吧:

正則表達式,又稱規則表達式。(英語:Regular Expression,在代碼中常簡寫爲regex、regexp或RE),計算機科學的一個概念。正則表達式通常被用來檢索、替換那些符合某個模式(規則)的文本。

試想一下如果要判斷010-1314520是否爲電話號碼,那麼通過python代碼實現邏輯大概如下(僞代碼):

if 是否是10個數字組成:
	if 是否有“-”:
		if 是否第4位是“-”
			print(“這是一個電話號碼結構”)

通過上面的僞代碼,我們發現至少需要進行三次判斷纔可以確定該字符串據具有電話號碼的結構。那如果用正則表達式又需要多少步呢?

import re
if re.compile(r"\d{3}-\d{7}").search("010-1314520"):
	print("這是一個電話號碼結構")

從行數上來看使用正則表達式也用了3行,但不同的是這是一個完整的代碼,可以直接使用。相對於if判斷語句利用正則表達式極大的簡化了代碼結構和運行效率更體現了程序的逼格。如果需要判斷更多字符串那麼正則表達式的優勢將更加明顯。這就是要學習正則表達式的主要原因:快速、準確、代碼精簡。

三、正則表達式組成體系

在python中要使用正則表達式,需要有三步:

  1. 首先需要通過命令import re導入正則表達式模塊,
  2. 其次再建立正則表達式對象(regex 對象),該對象直白的說就是定義自己的正則表達式語法,再後續的匹配中將使用該語法進行查找匹配。
  3. 對regex對象調用Search()方法,讓其返回match對象。regex對象的方法有:search(),findall(),sub()等,本文只介紹這三個方法。
  4. 對match對象使用group()方法返回匹配的文本內容。
    我們還是將上面的代碼作爲示例來演示,在上面的示例中爲了使代碼簡潔,我將正則表達式部分寫在了一起,下面按照正則表達式的組成部分來拆分代碼:
import re									#導入正則表達式庫
a = re.compile(r"\d{3}-\d{7}")				#創建regex對象
b = a.search("010-1314520")					#通過regex對象的search方法來查找,如果查找成功返回match對象,否則返回None
print("電話號碼是:%s" %b.group())			#通過group()方法讀出文本內容

運行結果如下:

電話號碼是:010-1314520

看到這裏可能大家要問,搜索了有啥用? 莫慌先讓我們看看這幾個方法的返回值

regex()方法 返回值 備註
search() search()方法查找傳入的字符串, 尋找該正則表達式的所有匹配。如果字符串中沒有找到該正則表達式模式, search()方法將返回 None。如果找到了該模式,search()方法將返回一個 Match 對象。以字符串形式返回第一個命中的內容 group(),groups()是search()的方法
findall() 以列表形式返回所有命中的字符串,未命中則返回空列表,如果存在分組,則分組以元組形式出現
sub() sub(參數1,參數2),用參數1替換參數2中被匹配的字符

回到前面的問題,來回答正則表達式在編程中到底能幹什麼,我這裏想到的是除了得到對應的返回值外我們可以根據返回值實現條件判斷、賦值、文本替換等功能。還有什麼高端騷操作請請速速打臉。
上面對正則表達式運行體系進行了介紹,下面將分別對每個組件進行解釋說明:

1. regex對象

regex對象這裏就一句話帶過了,不詳細講了,因爲創建regex對象的目的是制訂自己的正則表達式語法,而語法部分作爲重中之重將在後面講,請先稍安勿躁。現在先將幾個常用的方法給大家介紹了着。

a = re.compile(r"\d{3}-\d{7}")	

re.compile()就是創建regex對象,r"\d{3}-\d{7}" 就是正則表達式語法,r表示元字符,不需要轉義。

2. search()方法

search()方法的參數是需要查找的字符串,它將在傳入的字符串中進行匹配,而且只匹配第一次命中的字符串。如果字符串中沒有找到該正則表達式模式, search()方法將返回 None。如果匹配成功則search()方法將返回一個 Match 對象。 Match 對象有一個 group()方法,它返回被查找字符串中實際匹配的文本(稍後我會解釋分組)。示例如下,將之前的例子修改下,讓其打印search()的返回值和search().group()的返回值

import re									
a = re.compile(r"\d{3}-\d{7}")				#該語法的意思是匹配“三個數字-七個數字”這樣的結構的字符串
b = a.search("123-123456700000000000")					
print(b)
print(type(b))								#查看search()返回值的類型
print(b.group())

以下運行結果證明search()返回值是一個match對象,而match通過調用group()方法將返回匹配的文本內容。

<re.Match object; span=(0, 11), match=‘123-1234567’>
<class’re.Match’>
123-1234567
運行結果第一樣顯示未match對象;第二行判斷b的類型的確爲match類型;第三行顯示了匹配的文本內容。

3. match對象

match對象也一言以蔽之,本質就是search()方法的返回值,(想要看栗子還是上面那個栗子)如果search()命中了就返回match對象,否則返回None。match對象有group()groups()兩個方法,他們的作用是返回匹配的文本內容,區別在於在有分組(什麼是分組?返回什麼類型? 莫慌,後續道來。)情況下,返回的類型不一樣。group()返回的是字符串,而groups()返回的是元組。

四、正則表達式語法

在瞭解了正則表達式組成部分後,現在可以開始語法部分了。好好學習,天天向上。Let go!!

1. 分組

用一個靈魂三問開啓新篇章:何爲分組?怎麼實現分組?什麼場景需要分組?我們按照這三個問題來一一解答。
分組就是用括號將正則表達式分成不同的小組,每個小組用括號來隔開(如果需要匹配括號需要轉義" \( "或者" \) ")。

plain ="123-123456700000000000"								#假設這是一個被加了00000000000的電話號碼
regex1 = re.compile(r"\d\d\d-\d\d\d\d\d\d\d")				#參照組
regex2 = re.compile(r"(\d\d\d)-(\d\d\d\d\d\d\d)")			#通過括號將語法分爲兩個組,組號從左到右,編號從1開始

result1 = regex1.search(plain).group()
result2 = regex2.search(plain).group()

print(result1)
print(result2)

\d表示匹配所有數字。上面的語法表示匹配“xxx-xxxxxxx”這樣的內容(x表示數字)。那麼以下是上面的運行結果:

123-1234567
123-1234567

分組和不分組的group()結果都是一樣的,那爲什麼還要分組呢? 好,讓哥來告訴你,假設我現在只要電話號碼的區號那怎麼搞?
那我們就將電話號碼的每一部分給他標個號,這個號就是組的序號,此處敲黑板通過括號將語法分爲任意個組,組號從左到右,編號從1開始也就是上面的兩個括號,栗子還是那個栗子,此時我們使用帶參數的group()方法,尼瑪,還有參數,複雜不? 負責的告訴你,不復雜,因爲她的參數就是你想要的組的序號。比如上面我想要第一組 也就是區號, 那就group(1),想要電話號碼那就group(2),簡單不。 要是還不明白,建議默默點擊右上角XX。來嘛,來嘛,照顧下看不懂的人嘛,順便再演示下groups()方法,上栗子;

plain ="123-123456700000000000"
regex1 = re.compile(r"\d\d\d-\d\d\d\d\d\d\d")
regex2 = re.compile(r"(\d\d\d)-(\d\d\d\d\d\d\d)")

result1 = regex1.search(plain).group()
result2 = regex2.search(plain).group()
result3 = regex2.search(plain).group(1)			#待參數 組號1
result4 = regex2.search(plain).group(2)			#待參數 組號2
result5 = regex2.search(plain).groups()			#注意是group的複數形式,將以元組形式返回所有分組內容

print(result1)
print(result2)
print(result3)
print(result4)
print(result5)

運行結果如下:

123-1234567
123-1234567
123
1234567
(‘123’, ‘1234567’)

注意最後一行,這就是groups()以元組方式返回了所有的分組內容,明白不,這些懂分組,group()groups()了波?應該懂了吧。

2. “|”管道符

介紹完分組,後面來說說“|”管道符,管道符在正則表達式語法中定義多個對象,這對象之間是或的關係,只要命中就返回,如果存在多個可能被命中的字符,那麼只匹配第一個。問題在於怎麼判斷第一個呢? 繼續喫栗子:

注意 plain和plain2的內容順序不一樣哦
plain ="""
        xiaoming
        xiaohua
        xiaohong
                    """

plain2 ="""
        xiaohua
        xiaoming
        xiaohong
                    """

regex1 = re.compile(r"xiaoming|xiaohua")			#定義匹配xiaoming和xiaohua中的一個即可,注意"|"兩側的內容和regex2位置不一樣
regex2 = re.compile(r"xiaohua|xiaoming")			#

print(regex1.search(plain).group())					#在plain中匹配
print(regex2.search(plain).group())

print(regex1.search(plain2).group())				#在plain2中匹配
print(regex2.search(plain2).group())

運行結果如下:

xiaoming
xiaoming
xiaohua
xiaohua

可以看到前兩個運行結果是一樣的,後兩個運行的結果是一樣的。 那麼爲什麼返回的值會不一樣呢? 這裏要敲黑板了,前面說到如果存在多個可以匹配的值,則返回第一個匹配的值,怎麼判斷第一個? 不是通過判斷語法中正則表達式內容的順序,而是文本中第一個出現的負責正則表達式語法的文本。 很明顯plain首先出現xiaoming ; plain2首先出現xiaohua

3. 用?號實現可選匹配

什麼意思呢? 就是有些字符可以匹配,也可以不匹配。 場景就是類似“xxx-xxxxxxx”的電話號碼結構中可以匹配“-”,也可以不匹配“-”。因爲你不能保證所有的電話號碼格式都是通過“-”來分隔的。在喫栗子前,先提一下findall()方法,findall()是regex的方法,用於以列表形式返回所有符合條件的文本,該栗子將加入findall()調味:

import re
a = re.compile(r"\d{3}-?\d{7}")              #問號前面的"-"可以匹配,也可以不匹配
print(a.findall("123-1234567 0101234567"))	 #使用findall()返回一切皆有可能的文本

運行結果如下:

[‘123-1234567’, ‘0101234567’]

明白了波?? 中間的“-”有了問號的加持,顯得可有可無了。

4.用“*”實現0次或多次匹配

在正則表達式中*號 是神一樣的存在,表示匹配前一個字符或分組0次或多次。甜點如下:

plain="neeeeeeeeero"        #9個e

print(re.compile(r"ne*ro").search(plain).group())      #將匹配以上文本
print(re.compile(r"nx*e*ro").search(plain).group())		#多加了一個x,但是在後面跟了星號,表示這個x可以出現,也可以不出現。故該語句能正確匹配以上文本
print(re.compile(r"n(ee)*ro").search(plain).group())	#將報錯,因爲這裏是對(ee)分組進行多個,但是9個e不滿足(ee)的倍數,所以search()會返回None,而None是沒有任何方法的,故調用group()方法會報錯

neeeeeeeeero
neeeeeeeeero
Traceback (most recent call last):
File “111.py”, line 38, in
print(re.compile(r"n(ee)*ro").search(plain).group())
AttributeError: ‘NoneType’ object has no attribute ‘group’

是不是對*的用法瞭如指掌了???

5. 用“+”匹配一次或多次

同上面的*號,區別在於+必須至少匹配一次。

plain="neeeeeeeeero"        #9個e

print(re.compile(r"ne+ro").search(plain).group())      #將匹配以上文本
print(re.compile(r"nx+e+ro").search(plain).group())      #將至少匹配一次x,但是文本中沒有x,所以匹配報錯

+*要對比着來記憶

6. 用{}匹配特定次數

顧名思義,用{}控制前面的分組匹配的次數,這裏還有個知識點就是貪心模式和非貪心模式,因爲{x,y}有兩個參數,分別代表最少最多,那麼可能出現多種匹配結果,正則表達式怎麼去匹配,規定默認按照最多去匹配。 也就是默認是貪心模式。 那麼怎麼讓他按照最短匹配呢? 直接在{}後面加?即可。直接上栗子:

plain="hahahahahaha"        #5個ha

print(re.compile(r"(ha){1,}?").search(plain).group())       #將(ha)匹配一次或多次
print(re.compile(r"(ha){,1}").search(plain).group())         #將(ha)匹配0次或1次
print(re.compile(r"(ha){3,5}").search(plain).group())       #將(ha)匹配3次到5次,默認貪心模式,將匹配最多次數
print(re.compile(r"(ha){3,5}?").search(plain).group())      #將(ha)匹配3次到5次,按最短匹配

運行結果:

ha
ha
hahahahaha
hahaha

五、進階篇

1. findall()方法

在前面有提到findall()是regex的方法和search()屬於同一level,故他們之間是二選一的方法,findall()用於以列表形式返回所有符合條件的文本。但是需要注意的是如果正則表達式中有分組出現,那麼列表中將嵌套元組,元組中每個元素爲一個分組。栗子:

plain="tel 010-123-1234567 mobile 123-123-1234567"

print(re.compile(r"(\d{3})-(\d{3})-(\d{7})").findall(plain))		#正則表達式中有分組
print(re.compile(r"\d{3}-\d{3}-\d{7}").findall(plain))				#正則表達式中無分組

運行結果:

[(‘010’, ‘123’, ‘1234567’), (‘123’, ‘123’, ‘1234567’)]
[‘010-123-1234567’, ‘123-123-1234567’]
很明顯第一行的列表中顯示了嵌套了兩個元組,每個元組中的內容均爲正則表達式的一個分組。

2. 字符分類

字符分類是通過[]或者特定的字符來縮短正則表達式長度的一種方式。那麼要掌握這種方法需要涉及以下兩方面的知識:

  1. []的應用:[0-9]表示(0|1|2|3|4|5|6|7|8|9),類似的[a-zA-Z]表示匹配所有字母,[0-9a-zA-Z]表示匹配所有數字和字母
  2. 字符表:
縮寫字符分類 描述
\d 0 到 9 的任何數字
\D 除 0 到 9 的數字以外的任何字符
\w 任何字母、數字或下劃線字符(可以認爲是匹配“單詞”字符)
\W 除字母、數字和下劃線以外的任何字符
\s 空格、製表符或換行符(可以認爲是匹配“空白”字符)
\S 除空格、製表符和換行符以外的任何字符
xmasRegex = re.compile(r'\d+\s\w+')
print(xmasRegex.findall('12 drummers, 11 pipers, 10 lords, 9 ladies, 8 maids, 7 swans, 6 geese, 5 rings, 4 birds, 3 hens, 2 doves, 1 partridge'))

正則表達式\d+\s\w+匹配的文本有一個或多個數字(\d+), 接下來是一個空白字符(\s), 接下來是一個或多個字母/數字/下劃線字符(\w+)。 findall()方法將返回所有匹配該正則表達式的字符串, 放在一個列表中。

[‘12 drummers’, ‘11 pipers’, ‘10 lords’, ‘9 ladies’, ‘8 maids’, ‘7 swans’, ‘6 geese’, ‘5 rings’, ‘4 birds’, ‘3 hens’, ‘2 doves’, ‘1 partridge’]

這時候可以建立自己的字符分類了,比如只匹配元音字符,re.compile(r"[aeiouAEIOU]") 這裏還有個知識點,就是若在[]前面加上^表示匹配除了[]這裏面的字符,如re.compile(r"^[aeiouAEIOU]")表示匹配非元音字符。特別注意:[]裏面的字符不需要轉義。

3. ^和$

可以在正則表達式的開始處使用插入符號^,表明匹配必鬚髮生在被查找文本開始處。類似地,可以再正則表達式的末尾加上美元符號$,表示該字符串必須以這個正則表達式的模式結束。可以同時使用^$,表明整個字符串必須匹配該模式,也就是說,只匹配該字符串的某個子集是不夠的。來來繼續喫栗子:

plain = "you are a bad boy"
plain1 = "I said: you are a bad boy"

regex = re.compile(r"^y.*y$")		#匹配以y開始以y結束的字符串
print(regex.findall(plain))			#匹配成功
print(regex.findall(plain1))		#匹配失敗,因爲不是以y開始

運行結果如下:

[‘you are a bad boy’]
[]

特別注意:^表示從需要查找的文本的開始出進行匹配,而不是文本中間某處符合正則表達式即可。

4. 通配符“.”

在正則表達式中 句點 “.”表示匹配除換行符之外的所有字符。所以如果想匹配任何字符串可以用人r".*"來實現:

plain = "you are a bad boy"
plain1 = "I said: you are a bad boy\n????"

regex = re.compile(r".*")
print(regex.search(plain).group())
print(regex.search(plain1).group())

運行結果如下:

you are a bad boy
I said: you are a bad boy
運行結果顯示匹配出了plain的全部字符,但是plain1的\n???並沒有被匹配,因爲“\n”表示換行,通匹配是不能匹配的,如果要匹配怎麼辦呢?需要使用re.DOTALL參數。

plain1 = "I said: you are a bad boy\n????"

regex = re.compile(r".*",re.DOTALL)
print(regex.search(plain1).group())

運行結果:

I said: you are a bad boy
???

很明顯,全部命中。

正則表達式參數總結:

  1. re.I :表示匹配的時候不區分大小寫;
  2. re.VERBOSE:忽略正則表達式字符串中的空白符和註釋.,如果要將正則表達式寫成多行,需要三引號。
  3. re.DOTALL:匹配字符串中的換行符。

5. sub()方法

正則表達式不僅能找到文本模式, 而且能夠用新的文本替換掉這些模式。 Regex對象的 sub()方法需要傳入兩個參數。第一個參數是一個字符串, 用於取代發現的匹配。第二個參數是一個字符串,即正則表達式。 sub()方法返回替換完成後的字符串。例如, 在交互式環境中輸入以下代碼

plain1 = "I said: you are a bad boy\n????"

regex = re.compile(r"you are",re.DOTALL)
print(regex.sub("he is",plain1))

該代碼使用“he is” 替換字符串中的 “you are”,下面是運行結果:

I said: he is a bad boy
???

到此爲止,正則表達式常用方法就這麼多了,正則表達式理論簡單,但要用得好,還得用的多。

六、實踐

1. 待更新

七、參考資料

  1. 《Python學習手冊》
  2. 《Python編程快速上手-讓繁瑣工作自動化》
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章