搞懂Python正規表示式,這一篇就夠了

劉?發表於2023-04-22

本文程式碼基於Python3.11直譯器,除了第一次示例,程式碼將省略 import re 這個語句

所有示例程式碼均可以在我的github倉庫中的 code.py檔案內檢視

[我的倉庫](PythonLearinig/正規表示式 at main · saopigqwq233/PythonLearinig (github.com))

搞清楚Python正規表示式語法,這一篇就夠了

1.Python正規表示式匹配文字模式方法

正規表示式是一種快速從文字中匹配對應模式文字的表示式,在Python中所有的正規表示式函式都在模組re中。

其一般使用方法如下:

import re
mo1 = re.compile('Batman') # 先使用re的方法compile,compile的字串引數便是一個正規表示式
# compile將返回一個一個Regex物件,mo1就是對應正規表示式模式的物件
name1 = mo1.search('My favorite hero is Batman') # 使用mo1物件中search方法,這個方法的字串引數就是需要被查詢的字串
# 匹配成功,那麼將返回一個Match物件給name1,這個物件中有group()方法,它返回與正規表示式匹配的字串(有些情況不全是字串,我會在後面作解釋)
# 匹配失敗,返回None
print(name1.group())

輸出結果如下:

2.直接查詢模式

2.1直接查詢

上面的程式碼就是直接查詢模式,正規表示式字串是'Batman',則需要在字串'My favorite hero is Batman',尋找'Batman'

2.2管道匹配多種模式

有時,需要匹配的文字有多種可能,需要不同的正規表示式匹配模式,可以用‘|’這個符號來表示管道匹配,即匹配多種可能

示例如下:

mo2 = re.compile(r'Batman|Superman')
name2 = mo2.search('My favorite hero is Superman')
print(name2.group())

正規表示式是'Batman|Superman',那麼在search()的字串引數中,與之匹配的是'Superman',那麼返回的Match物件賦給name2,其方法group()返回’Superman‘

輸出結果如下:

2.3管道匹配多種分組模式

如果"我喜歡的英雄可能性有點大",正規表示式需要寫成'Batman|Superman|Spiderman'嗎?

可以,但可以用簡潔的形式'(Bat|Super|Spider)man'

那麼我們先看程式碼:

mo3 = re.compile('(Bat|Super|Spider)man')
name3 = mo3.search('My favorite hero is Spiderman')
print(name3.group())
print(name3.group(0))  # 0預設是整個匹配的字串
print(name3.group(1))  # 1是匹配的第一個分組

search()進行匹配時,先匹配第一個分組’Bat‘’Super‘’Spider‘中的一個,再匹配’man‘;也可以認為是匹配’Batman‘'Superman''Spiderman'中的一個。

這是執行情況:

需要指出的是,在正規表示式中出現分組時,可以在group中傳入引數,引數作為索引,比如在上述程式碼的group(1),此方法將返回第一個分組,同時,無引數或者引數為0則預設返回整個匹配文字

search()只會返回含有第一個出現的匹配文字的物件

先來看看這段程式碼:

mo4 = re.compile('(Bat|Super)man')
name4 = mo4.search('I love Superman and Batman')
print(name4.group())  # 只輸出第一個出現的Superman

執行結果如下:

這段程式碼中,可以正規表示式可以匹配的文字有’Superman‘和'Batman'兩個,但是name4物件的group方法只返回了第一個出現的'Superman'。

後面會有找到所有匹配文字的方法

3.查詢固定型別字元模式

3.1字元型別

縮寫字元 匹配字元
\d 0~9的數字
\D 除了0~9的其它字元
\w 字母,數字,下劃線
\W 除了字元數字下劃線
\s 空格製表換行符
\S 除了空格製表換行符

從上面表格可以看出了,大寫字母匹配的字元就是小寫字母匹配字元的補集

3.2固定型別模式

使用上面的縮寫字元,可以匹配指定型別的字元

如程式碼:

mo5 = re.compile(r'\d\d\d\d\d\d\d\d\d\d\d')
phone_number1 = mo5.search('我的電話號碼是15600000000')
print(phone_number1.group())

r字首用於表示字串是一個原始字串,避免轉義。

比如,如果無r字首,那麼字串中的'\n'將被解釋為換行符,但是如果加上r字首,那麼會被解釋為''和'n'兩個字元。

這在正規表示式使很有效,因為正規表示式是按照兩個字元''和'd‘來匹配一個數字型的字元,如果不加字首r,那麼我們需要在正規表示式中這樣寫'\\d','\'代表'',程式碼演示如下

mo = re.compile('\\d')
num = mo.search('abcd6ef')
print(num.group())

執行結果如下:

但是需要注意的是,以下幾個在正規表示式中有特殊含義的字元即使前面有r,仍然要加跳脫字元''來匹配這些特殊字元

| + | . | $ | * | ^ | ? | { | } | ( | ) | [ | ] | \ | | |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |

比如我們知道'(' ')'可以分組,但是想要在文字中匹配'('')'時,即使加了r字首,也需要''轉義

4.分組模式

有時我們需要給查詢到的電話號碼分段,比如前面的+86字首,這時,就可以用到分組模式

程式碼如下:

mo5 = re.compile(r'(\+86)(\d\d\d)(\d\d\d\d)(\d\d\d\d)')
phone_number1 = mo5.search('我的電話號碼是+8618900000000')
print('電話號碼'+phone_number1.group())
print('字首'+phone_number1.group(1))))

在書寫正規表示式的時候,給字首+86分了一組,其後按照我的閱讀習慣,344數量格式再分了三組

字串 組索引
+86 1
189 2
0000 3
0000 4

執行結果是

5.可有可無的分組的模式

不過,我們通常在平時記錄電話號碼時可能沒有+86這個字首,這時我們書寫正規表示式就可以使用字尾’?‘來修飾它前面的分組,表示前面這個分組在匹配文字時可有可無,示例程式碼如下:

mo6 = re.compile(r'(\+86)?(\d\d\d)(\d\d\d\d)(\d\d\d\d)')
phone_number2 = mo6.search('他輸入了+8615600000000到電話框')
phone_number3 = mo6.search('另一個人輸入18900000000')
print(phone_number2.group())
print(phone_number3.group())

在這個正規表示式中,我們對(+86)進行可選匹配,匹配結果有'+86',返回字串會帶'+86',反之不帶

第一個文字匹配返回物件中會包含返回'+8615600000000'的方法

第二個文字匹配返回物件中會包含返回'18900000000'的方法

執行結果:

6.含有特殊字元的模式

如我們在3.2中對特殊字元的討論,我們來分析一下下面的程式碼:

mo7 = re.compile(r'(\(\+\d\d\))(\d\d\d)(\d\d\d\d)(\d\d\d\d)')
phone_number4 = mo7.search('我的電話號碼是(+86)15600000000')
print(phone_number4.group())

r'(\(\+\d\d))(\d\d\d)(\d\d\d\d)(\d\d\d\d)'中,字首r表示後面的字串是原始字串

總共分了四組,分別是:

模式 組索引
(+\d\d) 1
\d\d\d 2
\d\d\d\d 3
\d\d\d\d 4

組1匹配小括號括起來的加號和兩個數字

組2匹配三個數字

組3、4匹配四個數字

執行結果是:

7.接收任意個數的字元的模式

被'*'修飾的字元或分組可以匹配0到多次,即在search()的文字中可以不出現也可以出現多次,示例程式碼如下:

mo8 = re.compile(r'\d*%')
money = mo8.search('本期漲幅有143%')
print(money.group())
money = mo8.search('本期漲幅為?%')
print(money.group())

在第一個正規表示式中,'\d*%'可以匹配'143%',其中'1' '4' '3'都與'\d'匹配

在第二個正規表示式中,'\d*%'可以匹配'%',因為'%'前沒有數字型別可以與'\d'匹配

下面是執行結果:

8.接收一個存在的連續字元模式

'+'修飾的字元或分組可以匹配1到多次,即在search()的文字中可以出現一次也可以出現多次,示例程式碼如下:

mo9 = re.compile(r'\d+')
numbers = mo9.search('第一產業增加值54779億元')
print(numbers.group())

這個正規表示式中,可以匹配到'54779'這5個數字,其中每個數字型文字都與'\d'匹配

如果沒有出現被'+'修飾的字元或者分組,會怎麼樣呢:

mo9 = re.compile(r'\d+億元')
numbers = mo9.search('第一產業增加值????億元')
print(type(numbers))

這裡如果我們直接輸出numbers.group()返回字串會報錯。馬上來解釋,現在我們用type()函式測試一下變數numbers的資料型別

執行結果:

可以看到,當沒找到時,search()方法返回了'NoneType',無型別,不是一個Match物件,自然是無法透過該變數獲得group()方法返回的字串。

事實上,search()方法在找不到匹配文字時就會返回一個NoneType型別

*和+的區別

修飾字元 匹配文字出現次數
* 0到多次
+ 1到多次

這意味著*修飾的字元或者分組可以不用出現

+修飾的字元或者分組必須出現

9.字元匹配固定次數

'{}'修飾的字元或分組可以按次數匹配

9.1固定次數

如果'{}'括號內只有一個整數,如'\d{3}',表示只匹配3個數字型別字元

程式碼示例如下:

mo10 = re.compile(r'(\+\d\d)?(\d){11}')
phone_number5 = mo10.search('電話號碼是15600000000')
print(phone_number5.group())

這段程式碼中,正規表示式r'(\+\d\d)?(\d){11}'表示'+\d\d'是可選匹配,可有可無,後面將匹配連續的11個數字型別字元,也就是說'15600000000'將匹配'\d'11次,執行結果如下:

當然,如果被查詢的文字中是類似於'+15600000000',到底是'+15'匹配'+\d\d',後面無法匹配11個數字字元,search()返回NoneType;還是'15600000000'匹配'\d{11}'呢?

我們試一試:

可以看到,匹配情況是剛剛描述的後者,即忽略'+',匹配後面的'\d{11}'

9.2次數範圍

'{}'內可以用逗號把兩個升序整數分開,比如'\d{11,13}',表示可以匹配11到13個數字字元

示例程式碼如下:

mo11 = re.compile(r'\+?\d{11,13}')
phone_number6 = mo11.search('電話號碼是+8615600000000')
print(phone_number6.group())

正規表示式r'\+?\d{11,13}'表示'+'是可選匹配,而'\d{11,13}'將匹配11到13個數字型別字元,在被查詢文字當中'+'匹配正規表示式的'\+?','8615600000000'匹配'\d{11,13}'

9.3貪心匹配與非貪心匹配

'{}'預設情況下匹配最多的字元,比如'\d{4,6}',被查詢文字是'1234567',那麼匹配結果是'123456',如果想要正規表示式匹配最少的字元,需要在'{}'後加上'?'修飾。這裡?不再表示可選匹配。

也就是說,正規表示式如果是'\d{4,6}?',那麼,匹配結果是'1234',返回最少的字元。

示例程式碼如下:

# 貪心匹配方式
mo12 = re.compile(r'\d{3,5}')
num1 = mo12.search('數字有34567')
print(num1.group())  # 匹配最多的數字

# 非貪心匹配
mo13 = re.compile(r'\d{3,5}?')
num2 = mo13.search('數字有34567')
print(num2.group())  # 匹配最少的數字

執行結果:

9.4注意

1)’{}‘內不可以出現浮點數,否則會報錯

2)'{}'允許'{3,3}'這樣的寫法,和'{3}'同義

3)'{a,b}',整數a必須不大於b

*查詢文字所有的匹配項findall()方法

上述用到的search()方法只能查詢到第一個出現的匹配文字項,如何找到全部匹配項呢?

使用Match物件的findall()方法,此方法可以返回匹配結果組成的列表,程式碼示例如下:

mo14 = re.compile(r'\d{11}')
phone_number7 = mo14.findall('電話號碼1:15600000000'
                             '電話號碼2:19100000000'
                             '電話號碼3:18700000000')
print(phone_number7)

我們想要在文字中找到所有和'\d{11}'能匹配的字元,'15600000000'等電話號碼都可以和正規表示式匹配,findall()將返回一個包含這些匹配文字的字串列表

執行結果如下:

與search()不同的是,findall()直接返回一個列表而不是Match物件,所以在上面千萬別把print(phone_number7)寫成print(phone_number7.group())了。

如果在正規表示式內用了分組,那麼會返回元組的列表,元組由分組的字串組成,這個返回結果不含有在正規表示式中未分組的部分

程式碼如下:

mo15 = re.compile(r'電話號碼\d:(\d{3})(\d{4})(\d{4})')
phone_number8 = mo15.findall('電話號碼1:15600000000'
                             '電話號碼2:19100000000'
                             '電話號碼3:18700000000')
print(phone_number8)

這裡,我們分別對電話號碼 前三位,中間四位,最後四位 分組,那麼單個匹配文字會被分成三個字串,組成一個元組,而這些元組組合成一個列表

執行結果如下:

10.自定義匹配字元型別

10.1匹配指定字元

在正規表示式中使用'[]'可以自己定義匹配字元,比如我想找到一個句子裡面所有母音開頭的字母

程式碼示例如下:

mo16 = re.compile(r'\b[aeiouAEIOU]\w*')
vowel_word = mo16.findall('I am obviously angry with you')
print(vowel_word)

這裡先介紹一下'\b'這個字元,這個字元將匹配單詞的分界,也就是說將從一個單詞開始匹配。

在這個字串文字中,單詞有'I' 'am' 'obviously' 'angry' 'with' 'you',

使用自定義匹配字元[aeiouAEIOU]匹配母音開頭,'\w*'匹配除了空格,製表符,換行符外的字元。

10.2匹配指定的字元無需加\轉義

在前面我們知道正規表示式的特殊字元前仍然需要加上''來轉義表示原字元

但是,在指定字元匹配當中,無需加''轉義,示例程式碼如下:

mo17 = re.compile(r'[*?+]+')
special_character = mo17.findall('*+?11*?')
print(special_character)

在這個正規表示式中,特殊字元'*' '?' '+'前並未加''轉義,將匹配連續的幾個指定字元組成的字串,被匹配文字中'*+?'和'*?'符合

執行結果如下:

10.3匹指定字元外的字元

在指定匹配字元的前面加上^表示不匹配這些字元

程式碼如下:

mo18 = re.compile(r'\b[^aeiouAEIOU\n\t ]\w*')
non_vowel_word = mo18.findall('I am obviously angry with you')
print(non_vowel_word)

在這個正規表示式中,'^'表示不匹配母音字元和換行符,製表符和' '空格符。也就是說,這個正規表示式匹配非母音字母開頭單詞

執行結果如下:

11.^和$在正規表示式中的作用

11.1^的作用

在正規表示式前加上'^',將會怎麼匹配呢?我們先看一下程式碼:

mo19 = re.compile(r'^(name):(\d)+')
name_phone1 = mo19.search('name:15600000000這是資訊的格式')
print(name_phone1.group())
name_phone1 = mo19.search('資訊的格式是name:15600000000')
print(type(name_phone1))

這裡出現兩個文字,但是隻有第一個可以匹配成功,第二個匹配失敗返回NoneType型別,這是為什麼呢?是因為'^'在正規表示式開頭的作用就是讓匹配字元必須從被檢查字串開頭開始匹配。第一個開頭就是可以匹配的'name:156000000000',而第二個雖然也有這樣的字串,但是並非從開頭開始匹配,便不會返回Match物件。

11.2$的作用

你知道我要說什麼

mo20 = re.compile(r'(name):(\d)+$')
name_phone2 = mo20.search('資訊的格式是name:15600000000')
print(name_phone2.group())
name_phone2 = mo20.search('name:15600000000這是資訊的格式')
print(type(name_phone2))

與'^'相反,'$'字元將讓正規表示式匹配字串末尾的文字,比如上面的兩個字串,幾乎可以直接推斷出可以匹配的是第一個

以下是執行結果:

12.通配字元‘.’,匹配除了換行符的所有字元

正規表示式中,'.'可以匹配一個任意除了換行符的字元。

mo21 = re.compile(r'.at')
words = mo21.findall('The cat in the hat sat on the flat mat')
print(words)

正規表示式將匹配末尾帶有'at'的所有字串,除了'at'位於一行開頭這種情況,看看結果吧:

12.1 '.*'匹配所有的字元

透過前面的關於'*'我們知道知道被'*'修飾的正規表示式字元將匹配0到多個。'*'同樣可以修飾'.',來達到匹配除了換行符外所有的字元的效果

而經過'*'修飾的'.'存在貪心匹配和非貪心匹配的情況。非貪心即在'*'後加上'?'

12.1.1貪心匹配(匹配最多的字串)

mo22 = re.compile(r'names:(.*) phone number:(.*)')
users_info = mo22.findall('names:Mike phone number:15600000000 '
                          'names:Jack phone number:18100000000 '
                          'names:John phone number:16200000000 ')
print(users_info)

正規表示式將匹配'names:'開頭加上其後的所有字元,直到遇到換行符或者字串最後為止。

注意我這裡的被查詢字串是由空格分開而非換行符,為方便看每行內容我用三個 '' 分開了字串。

先看看執行結果:

從返回結果來看

1)列表中只含一個元組,說明整個正規表示式只匹配了一個字串

2)findall返回分組的匹配字串,從中可以推斷出,正規表示式'names:'匹配了被查詢字串的'names:',正規表示式'(.)'匹配了從'Mike'到'John'的所有字元,正規表示式' phone number:'匹配了被查詢字元的'phone number:',第二個'(.)'匹配了'16200000000'

12.1.2非貪心匹配(匹配最少的字串)

在'*'修飾後加上'?'就可以改成非貪心匹配。程式碼如下:

mo23 = re.compile(r'names:(.*?) phone number:(.*?) ')
users_info = mo23.findall('names:Mike phone number:15600000000 '
                          'names:Jack phone number:18100000000 '
                          'names:John phone number:16200000000 ')
print(users_info)

先來看看執行結果:

列表中有三個元組,說明被查詢的字串中有三組字串和正規表示式匹配成功。

1)第一組匹配:'names:'匹配'names:','(.*?)'匹配'Mike',空格匹配空格,'phone number'匹配'phone number','(.*?)'匹配'15600000000'

2)3)同1)

12.2 '.*'匹配換行符

事實上可以透過向compile()方法傳關鍵字引數就可以讓'.*'匹配換行符。

我們先看看不加關鍵字實參的情況

mo24 = re.compile('.*')
sentences = mo24.search("I can see empty streets.\nBut I can't sleep empty sheets\n")
print(sentences.group())

由於'.'不能匹配換行符,所以文字只能匹配到'I can see empty streets.'

來看看執行結果:

顯然我們的判斷沒錯

現在我們將關鍵字實參re.DOTALL傳入compile()方法

mo25 = re.compile('.*', re.DOTALL)
sentences = mo25.search("I can see empty streets.\nBut I can't sleep empty sheets\n")
print(repr(sentences.group()))
print(sentences.group())

容我先解釋一下 repr(str)函式的作用,它返回字串str的原字串,不進行轉義。

在這個正規表示式中,'.*'將匹配所有的字元,那麼,search()將要返回的是,包含所有被查詢文字字元的物件

我們來看看執行結果:

可以看到,repr()返回的字串內含有所有被查詢文字,包括換行符也被'.'匹配

13.模糊大小寫

我們在輸入驗證碼驗證自己不是機器人時,往往大小寫均可以讓自己透過。那麼在正規表示式當中,我們也想在匹配時忽略大小寫。只需要向compile()方法傳入關鍵字實參re.IGNORECASE即可,速記一下就是ignore case大寫並且去空格

mo26 = re.compile(r'england|china|america', re.IGNORECASE)
country_name = mo26.search("THE PEOPLE'S REPUBLIC OF CHINA")
print(country_name.group())

由於大小寫模糊,'CHINA'和正規表示式中的'china'匹配

執行結果:

14.更好得書寫管理正規表示式

過於複雜的正規表示式將變得難以理解,那麼可以透過以下幾個方法讓正規表示式易理解:

14.1多段 '' 連線

比如我們想在一串文字中找出大陸的電話號碼,使用 r'' 分行並寫下注釋,那麼可以透過以下示例程式碼查詢:

mo27 = re.compile(r'(\+86)?'  # 大陸電話字首
                  r'(\d\d\d)'  # 電話前三位
                  r'(\d\d\d\d)'  # 電話中間四位
                  r'(\d\d\d\d)')  # 電話後四位
phone_number9 = mo27.search('電話號碼是:15600000000')
print(phone_number9.group())

執行結果:

14.2關鍵字實參re.VERBOSE

可以使用關鍵字實參和多行字串''' ''' 標識,來使正規表示式更易讀。

mo28 = re.compile(r'''(\+86)?  # 大陸電話字首
                      (\d{3})  # 電話前三位
                      (\d{4})  # 電話中間四位
                      (\d{4})  # 電話後四位
                      ''', re.VERBOSE)
phone_number10 = mo28.search('電話號碼是:15600000000')
print(phone_number10.group())

執行結果:

15.多個關鍵字實參

如果我們想要正規表示式匹配時模糊大小寫,並且讓'.'可以匹配到換行符,直接這樣寫mo = re.compile(r'',re.DOTALL,re.IGNORECASE)

是不被允許的,因為compile()最多隻有兩個引數

那麼,如何解決呢?

可以在幾個關鍵字實參之間用'|'間隔達到多個關鍵字實參的效果

mo29 = re.compile(r'nice to meet you,.*',re.DOTALL|re.IGNORECASE)
response = mo29.search('NICE to Meet You,Sir.\nHow can I help you?')
print(repr(response.group()))
print(response.group())

這樣,既可以有 模糊大小寫的效果,也有讓 '.'匹配換行符的效果

執行效果:


感謝你閱讀我的部落格,如果你對我的內容有任何的意見、建議或者問題,歡迎在評論區留言,我會盡快回復。如果你發現了我的錯誤或者疏漏,也請不吝指正,我會及時修改。希望我的部落格能對你有所幫助,也期待與你的交流和分享。

相關文章