1. 程式人生 > >Python 正則表示式,實戰篇! 再搞不懂? 算我輸~

Python 正則表示式,實戰篇! 再搞不懂? 算我輸~

整理自: Automate the Boring Stuff with Python 作者: Al Sweigart

1. 建立正則表示式物件和匹配Regex物件

re.compile() 傳入一個字串值,表示正則表示式,它將返回一個 Regex 模式 物件(或者就簡稱為 Regex 物件)。Regex 物件的 search() 方法查詢傳入的字串,尋找該正則表示式的所有匹配。如果字串中沒有找到該正則表示式模式,search() 方法將返回 None。如果找到了該模式, search() 方法將返回一個 Match 物件。Match 物件有一個 group() 方法,它返回被查詢字串中實際匹配的文字。

import re
# 因為正則表示式常常使用倒斜槓,向 re.compile()函式傳入原始字串就很方便
# r: 忽略所有的轉義字元,打印出字串中所有的倒斜槓
phoneNumber = re.compile(r'\d{3}-\d{3}-\d{3}') # Regex 物件
mo = phoneNumber.search('Mu Number is 400-000-999') # 返回一個 Match 物件
mo.group()
>> '400-000-999'

2. 正則表示式匹配更多模式

2.1 利用括號分組

新增括號將在正則表示式中建立“分組”: (\d\d\d)-(\d\d\d-\d\d\d\d)。然後可以使用 group()匹配物件方法,從一個分組中獲取匹配的文字。

phoneNumRegex = re.compile(r'(\d\d\d)-(\d\d\d-\d\d\d\d)')
mo = phoneNumRegex.search('My number is 415-555-4242.')
print(mo.group(1))
print(mo.group(2))
#一次就獲取所有的分組 
print(mo.groups())

>> 415-555-4242
>> 555-4242
>> ('415', '555-4242')

2.2 在文字中匹配括號

括號在正則表示式中有特殊的含義,但是如果你需要在文字中匹配括號,怎麼辦?例如,你要匹配的電話號碼,可能將區號放在一對括號中。傳遞給 re.compile()的原始字串中,(和)轉義字元將匹配實際的括號字元。

 #phoneNumRegex = re.compile(r'(\(\d\d\d\)) (\d\d\d-\d\d\d\d)')
phoneNumRegex = re.compile(r'(\(\d{3}\)) (\d{3}-\d{4})')
mo = phoneNumRegex.search('My phone number is (415) 555-4242.')
print(mo.group(1))
>> (415)

3. 用管道匹配多個分組

字元 | 稱為 管道。希望匹配許多表達式中的一個時,就可以使用它。例如, 正則表示式 r**Batman | Tina Fey **將匹配 BatmanTina Fey。 如果 BatmanTina Fey 都出現在被查詢的字串中,第一次出現的匹配文字, 將作為 **Match **物件返回。

heroRegex = re.compile(r'Brain|head') # 中間不要有空格,不然第二個列印報錯
mo1 = heroRegex.search('Brain and head')
print(mo1.group())
mo2 = heroRegex.search('head and Brain')
mo2.group()
print(mo2.group())

>> Brain
>> head

3.1 匹配多個模式中的一個

batRegex = re.compile(r'Bat(man|mobile|copter|bat)')
mo = batRegex.search('Batmobile lost a wheel')
print(mo.group())
print(mo.group(1)) # 只是返 回第一個括號分組內匹配的文字'mobile'
print(mo.groups())

>> Batmobile
>> mobile
>> ('mobile',)

4. 用問號實現可選匹配:

匹配的模式是可選的。就是說,不論這段文字在不在,正則表示式 都會認為匹配。字元 ? 表明它前面的分組在這個模式中是可選的,如果需要匹配真正的問號字元,就使用轉義字元 \?

batRegex = re.compile(r'Bat(wo)?man')
mo1 = batRegex.search('The Adventures of Batman')
print(mo1.group())
mo2 = batRegex.search('The Adventures of Batwoman')
print(mo2.group())
print(mo2.group(1)) # 這個才有分組,因為匹配的物件有:“wo”;  (wo)?部分表明,模式 wo 是可選的分組

>> Batman
>> Batwoman
>> wo

4.1.1 讓正則表示式尋找包含區號或不包含區號的電話號碼

phoneRegex = re.compile(r'(\d{3}-)?\d{3}-\d{4}')
mo1 = phoneRegex.search('My Number is 411-234-4566')
# 建議都打出來看看
print(mo1.group())
print(mo1.group(1))
print(mo1.groups())

mo2 = phoneRegex.search('My Number is 222-3445')
print(mo2.group())
print(mo2.group(1)) # 沒有可選的組,為None
print(mo2.groups()) # 同上

>> 411-234-4566
>> 411-
>> ('411-',)
>> 222-3445
>> None
>> (None,)

5. 用星號匹配零次或多次

* (稱為星號)意味著 匹配零次或多次,即星號之前的分組,可以在文字中出現任意次。它可以完全不存在,或一次又一次地重複。

// An highlighted block
batRegex = re.compile(r'Bat(wo)*man')
mo1 = batRegex.search('The Adventures of Batman')
print(mo1.group())

# 下面就有group(1)了
mo2 = batRegex.search('The Adventures of Batwoman')
print(mo2.group())
print(mo2.group(1))

mo3 = batRegex.search('The Adventures of Batwowowoman')
print(mo3.group())
print(mo3.group(1)) # group(1)與上面是相同,只看條件,不看量

>> Batman
>> Batwoman
>> wo
>> Batwowowoman
>> wo

6. 用加號匹配一次或多次

*意味著匹配零次或多次,+(加號)則意味著 匹配一次或多次星號 不要求分組出現在匹配的字串中,但加號不同,加號前面的分組必須 至少出現一次。這不是可選的。

batRegex = re.compile(r'Bat(wo)+man')
mo1 = batRegex.search("The Adventures of Batwoman")
print(mo1.group())

mo2 = batRegex.search("The Adventures of Batwowowoman")
print(mo2.group())

# 加號 要求 wo 至少出現一次。
# 如果需要匹配真正的加號字元,在加號前面加上倒斜槓實現轉義:\+
mo3 = batRegex.search("The Adventures of Batman")
print(mo3 == None)
>> Batwoman
>> Batwowowoman
>> True

7. 用加號匹配一次或多次

如果想要一個分組重複特定次數,就在正則表示式中該分組的後面,跟上花括 號包圍的數字。例如,正則表示式 (Ha){3} 將匹配字串 ‘HaHaHa’,但不會匹配 ‘HaHa’, 因為後者只重複了 (Ha)分組兩次。除了一個數字,還可以指定一個範圍,即在花括號中寫下一個最小值、一個逗號和 一個最大值。例如,正則表示式 (Ha){3,5} 將匹配’HaHaHa’‘HaHaHaHa’’HaHaHaHaHa’。也可以不寫花括號中的第一個或第二個數字,不限定最小值或最大值。例如,(Ha){3,} 將匹配 3 次或更多次例項,(Ha){,5} 將匹配 0 到 5 次例項。花括號讓正則表示式更簡短;

  • (Ha){3} (Ha)(Ha)(Ha)

  • (Ha){3,5} ((Ha)(Ha)(Ha)) | ((Ha)(Ha)(Ha)(Ha)) | ((Ha)(Ha)(Ha)(Ha)(Ha))

    上面二者兩組的正則表示式匹配同樣的模式

haRegex = re.compile(r'(Ha){3}')
mo1 = haRegex.search('HaHaHa')
print(mo1.group())
mo2 = haRegex.search('HaHa')
print(mo2 == None)

>> HaHaHa
>> True

8. 貪心和非貪心匹配

如: (Ha){3,5} 的匹配模式,會返回: ‘HaHaHaHaHa’; Python的正則表示式預設是 “貪心” 的,這表示在有二義的情況下,它們會盡可能匹配最長的字串。花括號的 “非貪心” 版本匹配儘可能最短的字串,即在結束的花括號後跟著一個問號。 注意: 問號在正則表示式中可能有兩種含義: 宣告非貪心匹配或表示可選的分組。這兩種含義是完全無關的。

greedyHaRegex = re.compile(r'(Ha){3,5}')
mo1 = greedyHaRegex.search('HaHaHaHaHa')
print(mo1.group())
nongreedyHaRegex = re.compile(r'(Ha){3,5}?')
mo2 = nongreedyHaRegex.search('HaHaHaHaHa')
print(mo2.group())

>> HaHaHaHaHa
>> HaHaHa

9. findall()方法

Regex 物件有一個 findall() 方法。search() 將返回一個 Match 物件,包含被查詢字串中的 第一次 匹配的文字,而 findall() 方法將返回一組字串,包含被查詢字串中的所有匹配;

9.1 search() 和 findall() 返回值的對比

phoneNumRegex = re.compile(r'\d{3}-\d{3}-\d{4}')
mo = phoneNumRegex.search('Wang:222-111-3333,Work:111-666-0000')
print(mo.group())

#findall()不是返回一個 Match 物件,而是返回一個字串列表,只要在正則表示式中沒有分組。
#列表中的每個字串都是一段被查詢的文字,它匹配該正則表示式
phoneList = phoneNumRegex.findall('Wang:222-111-3333,Work:111-666-0000')
print(phoneList)

>> 222-111-3333
>> ['222-111-3333', '111-666-0000']

9.2 findall()分組情況,返回列表

#如果在正則表示式中有分組,那麼 findall 將返回元組的列表。
#每個元組表示一個找到的匹配,其中的項就是正則表示式中每個分組的匹配字串
phoneNumRegex = re.compile(r'(\d{3})-(\d{3})-(\d{4})')
tuples = phoneNumRegex.findall('Wang:222-111-3333,Work:111-666-0000')
print(tuples)

>> [('222', '111', '3333'), ('111', '666', '0000')]

10. 字元分類總結

\d 是正則表示式 (0|1|2|3|4|5|6|7|8|9) 的縮寫;

常用字元分類的縮寫程式碼:

\d: 0 到 9 的任何數字

\D: 除 0 到 9 的數字以外的任何字元

\w: 任何字母、數字或下劃線字元(可以認為是匹配“單詞”字元)

\W: 除字母、數字和下劃線以外的任何字元

\s: 空格、製表符或換行符(可以認為是匹配“空白”字元)

\S: 除空格、製表符和換行符以外的任何字元

如: 字元分類**[0-5]**只匹配數字 0 到 5

xmasRegex = re.compile(r'\d+\s\w+')
xmas = 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')
print(xmas)
#解釋: 正則表示式\d+\s\w+匹配的文字有一個或多個數字(\d+),接下來是一個空白字 (\s),接下來是一個或多個字母/數字/下劃線字元(\w+)

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

11. 建立自己的字元分類

想匹配一組字元,但縮寫的字元分類 (\d、\w、\s 等) 太寬泛。可以用方括號定義自己的字元分類。例如,字元分類 [aeiouAEIOU] 將匹配所有母音字元,不論大小寫;也可以使用短橫表示字母或數字的範圍。例如,字元分類 [a-zA-Z0-9] 將匹配所有小寫字母、大寫字母和數字。 注意: 在方括號內,普通的正則表示式符號不會被解釋,意味著,不需要前面加上倒斜槓轉義**. 、*、?或()字元**。例如,字元分類將匹配數字 0 到 5 和一個 句點。你不需要將它寫成 [0-5.]

通過在字元分類的左方括號後加上一個插入字元 (^),就可以得到 非字元類 。 非字元類 將匹配不在這個字元類中的所有字元。

11.1 非 ^ 的情況

vowelRegex = re.compile(r'[aeiouAEIOU]')
vowel = vowelRegex.findall('RoboCop eats baby food. BABY FOOD.')
print(vowel)
>> ['o', 'o', 'o', 'e', 'a', 'a', 'o', 'o', 'A', 'O', 'O']

11.2 有 ^ 的情況

# 通過在字元分類的左方括號後加上一個插入字元 **(^)**,就可以得到“非字元類”。 非字元類將匹配不在這個字元類中的所有字元

consonanRegex = re.compile(r'[^aeiouAEIOU]')
consonan = consonanRegex.findall('RoboCop eats baby food. BABY FOOD.')
print(consonan)
>> ['R', 'b', 'C', 'p', ' ', 't', 's', ' ', 'b', 'b', 'y', ' ', 'f', 'd', '.', ' ', 'B', 'B', 'Y', ' ', 'F', 'D', '.']

12. 插入字元和美元字元

在正則表示式的開始處使用插入符號 “(^)”,表明匹配必須發生在被查詢文字開始處。類似地,可以再正則表示式的末尾加上美元符號 "()"使)"** ,表示該字串必須以這個正則表示式的模式結束。可以同時使用 **^** 和 **,表明整個字串必須匹配該模式,也就是說,只匹配該字串的某個子集是不夠的。

12.1 使用^ 的情況

beginWithHello = re.compile(r'^Hello')
beginWith1 = beginWithHello.search('Hello world')
print(beginWith1.group())

beginWith2 = beginWithHello.search('He said hello')
print(beginWith2 == None)
>> Hello
>> True

12.2 使用$ 的情況

#表示式 r'\d$'匹配以數字 09 結束的字串
endWithNumber = re.compile(r'\d$')
endWith1 = endWithNumber.search('Your number is 000')
print(endWith1.group())

endWith2 = endWithNumber.search('Your number is two')
print(endWith2 == None)

>> 0
>> True

12.3 使用 “+$ ”的情況

# 正則表示式 r'^\d+$'匹配從開始到結束都是數字的字串。
wholeStringIsNum = re.compile(r'^\d+$')
whole1 = wholeStringIsNum.search('1234567890')
print(whole1.group())

whole2 = wholeStringIsNum.search('1234567ss890')
print(whole2 == None)

whole3 = wholeStringIsNum.search('123456  7ss890')
print(whole3 == None)

>> 1234567890
>> True
>> True

13. 通配字元

’.(句號)’ 稱為萬用字元。 匹配除了換行之外的所有字元。

注意: 句點字元只匹配一個字元,要匹配真正的句點,就是用倒斜槓轉義: \

atRegex = re.compile(r'.at')
at = atRegex.findall('The cat in the hat sat on the flat mat.')
print(at)
>> ['cat', 'hat', 'sat', 'lat', 'mat']

13.1 用點-星匹配所有字元

有時候想要匹配所有字串。例如,假定想要匹配字串 ‘First Name:’,接下來 是任意文字,接下來是 ‘Last Name:’,然後又是任意文字。 可以用 點-星(.*) 表示“任意文字”。句點字元表示 “除換行外所有單個字元”,星號字元表示 “前面字元出現零次或多次”

nameRegex = re.compile(r'First Name: (.*) Last Name: (.*)')
mo = nameRegex.search('First Name: AI Last Name: Sweety')
print(mo.group(1))
print(mo.group(2))

mo1 = nameRegex.findall('First Name: AI Last Name: Sweety')
print(mo1)

>> AI   # AI後 Last Name 前,跟再多的單詞都會被提出來
>> Sweety
>> [('AI', 'Sweety')]
#點-星使用“貪心”模式:它總是匹配儘可能多的文字。要用“非貪心”模式匹配所有文字,就使用點-星和問號。
#像和大括號一起使用時那樣,問號告訴 Python 用非貪心模式匹配。

#貪心模式和非貪心模式的區別
nongreedyRegex = re.compile(r'<.*?>')
mo = nongreedyRegex.search('<To serve man> for dinner.>')
print(mo.group())
greedyRegex = re.compile(r'<.*>')
mo1 = greedyRegex.search('<To serve man> for dinner.>')
print(mo1.group())
>> <To serve man>
>> <To serve man> for dinner.>

"""
解釋:
兩個正則表示式都可以翻譯成“匹配一個左尖括號,接下來是任意字元,接下來是一個右尖括號”。
但是字串'<To serve man> for dinner.>'對右肩括號有兩種可能的匹配。
在非貪心的正則表示式中,Python 匹配最短可能的字串:'<To serve man>'。 
在貪心版本中,Python 匹配最長可能的字串:'<To serve man> for dinner.>'"""

13.2 用句點字元匹配換行

點-星將匹配除換行外的所有字元。通過傳入 re.DOTALL 作為 **re.compile()**的第 二個引數,可以讓句點字元匹配所有字元,包括換行字元。

noNewlineRegex = re.compile('.*')
noNewline = noNewlineRegex.search('Serve the public trust.\nProtect the innocent. \nUphold the law.').group()
print(noNewline)

newlineRegex = re.compile('.*', re.DOTALL)
newline = newlineRegex.search('Serve the public trust.\nProtect the innocent.\nUphold the law.').group()
print(newline)

>> Serve the public trust.
>> Serve the public trust.
>> Protect the innocent.
>> Uphold the law.
"""
j解釋:
正則表示式 noNewlineRegex 在建立時沒有向 re.compile()傳入 re.DOTALL,它將匹配所有字元,直到第一個換行字元。
但是,newlineRegex 在建立時向 re.compile()傳 入了 re.DOTALL,它將匹配所有字元。

"""

14. 正則表示式符號複習

?: 匹配零次或一次前面的分組。

*: 匹配零次或多次前面的分組。

+: 匹配一次或多次前面的分組。

{n}: 匹配 n 次前面的分組。

{n,}: 匹配 n 次或更多前面的分組。

{,m}: 匹配零次到 m 次前面的分組。

{n,m}: 匹配至少 n 次、至多 m 次前面的分組。

{n,m}?或*?或+?: 對前面的分組進行非貪心匹配。

^spam: 意味著字串必須以 spam 開始。

spam$: 意味著字串必須以 spam 結束。

. 匹配所有字元,換行符除外。

\d、\w 和\s: 分別匹配數字、單詞和空格。

\D、\W 和\S: 分別匹配出數字、單詞和空格外的所有字元。

[abc]: 匹配方括號內的任意字元(諸如 a、b 或 c)。

[^abc]: 匹配不在方括號內的任意字元。

15. 不區分大小寫

#表示式匹配 完全不同的字串:
regex1 = re.compile('RoboCop')
regex2 = re.compile('ROBOCOP') 
regex3 = re.compile('robOcop')
regex4 = re.compile('RobocOp')

"""
有時候只關心匹配字母,不關心它們是大寫或小寫。
要讓正則表示式 不區分大小寫,可以向 re.compile()傳入 re.IGNORECASE 或 re.I,作為第二個引數。
"""
robocop = re.compile(r'robocop', re.I)
ro1 = robocop.search('RoboCop is part man, part machine, all cop.').group()
print(ro1)

ro2 = robocop<