Python 模式匹配與正則表達(dá)式
1. 模式匹配與正則表達(dá)式
你可能熟悉文本查找,即按下Ctrl-F,輸入你要查找的詞。 “正則表達(dá)式”更進(jìn)一步,它們讓你指定要查找的“模式”。 你也許不知道一家公司的準(zhǔn)確電話號碼,但如果你住在美國或加拿大, 你就知道它有3位數(shù)字,然后是一個(gè)短橫線,然后是4位數(shù)字(有時(shí)候以3位區(qū)號開始)。 因此作為一個(gè)人,你看到一個(gè)電話號碼就知道: 415-555-1234 是電話號碼,但 4,155,551,234 不是。
正則表達(dá)式很有用,但如果不是程序員,很少會有人了解它, 盡管大多數(shù)現(xiàn)代文本編輯器和文字處理器(諸如微軟的Word或OpenOffice/LibreOffcie), 都有查找和查找替換功能,可以根據(jù)正則表達(dá)式查找。 正則表達(dá)式可以節(jié)約大量時(shí)間,不僅適用于軟件用戶,也適用于程序員。 實(shí)際上,技術(shù)作家Cory Doctorow聲稱,甚至應(yīng)該在教授編程之前,先教授正則表達(dá)式:
“知道[正則表達(dá)式]可能意味著用3步解決一個(gè)問題, 而不是用3000步。如果你是一個(gè)技術(shù)怪俠, 別忘了你用幾次擊鍵就能解決的問題, 其他人需要數(shù)天的煩瑣工作才能解決, 而且他們?nèi)菀追稿e(cuò)?!?/p>
在本章中,你將從編寫一個(gè)程序開始,先不用正則表達(dá)式來尋找文本模式。 然后再看看,使用正則表達(dá)式讓代碼變得多么簡潔。 我將展示用正則表達(dá)式進(jìn)行基本匹配,然后轉(zhuǎn)向一些更強(qiáng)大的功能,諸如字符串替換, 以及創(chuàng)建你自己的字符類型。
1.1. 不用正則表達(dá)式來查找文本模式
假設(shè)你希望在字符串中查找電話號碼。你知道模式:3個(gè)數(shù)字, 一個(gè)短橫線,3個(gè)數(shù)字,一個(gè)短橫線,再是4個(gè)數(shù)字。 例如:415-555-4242。
假定我們用一個(gè)名為 isPhoneNumber() 的函數(shù), 來檢查字符串是否匹配模式,它返回 True 或 False 。 打開一個(gè)新的文件編輯器窗口,輸入以下代碼, 然后保存為 isPhoneNumber.py :
def isPhoneNumber(text):
if len(text) !=12:
return False
for i in range(3):
if not text[i].isdecimal():
return False
if text[3]!=‘-’:
return False
for i in range(4,7):
if not text[i].isdecimal():
return False
if text[7] != ‘-’:
return False
for i in range(8,12):
if not text[i].isdecimal():
return False
return True
print(‘415-555-4242 is a phone number:’)
print(isPhoneNumber(‘415-555-4242’))
print(‘Moshi moshi is a phone number:’)
print(isPhoneNumber(‘Moshi moshi’))
415-555-4242 is a phone number:
True
Moshi moshi is a phone number:
False
isPhoneNumber() 函數(shù)的代碼進(jìn)行幾項(xiàng)檢查, 看看 text 中的字符串是不是有效的電話號碼。 如果其中任意一項(xiàng)檢查失敗,函數(shù)就返回 False 。 代碼首先檢查該字符串是否剛好有12個(gè)字符。 然后它檢查區(qū)號(就是 text 中的前3個(gè)字符)是否只包含 數(shù)字。函數(shù)剩下的部分檢查該字符串是否符合電話號碼的模式: 號碼必須在區(qū)號后出現(xiàn)第一個(gè)短橫線), 3個(gè)數(shù)字,然后是另一個(gè)短橫線,最后是4個(gè)數(shù)字。 如果程序執(zhí)行通過了所有的檢查,它就返回 True 。
用參數(shù) ‘415-555-4242’ 調(diào)用 isPhoneNumber() 將返回真。 用參數(shù) ‘Moshimoshi’ 調(diào)用 isPhoneNumber() 將返回假, 第一項(xiàng)測試失敗了,因?yàn)椴皇?2個(gè)字符。
必須添加更多代碼,才能在更長的字符串中尋找這種文本模式。 用下面的代碼, 替代 isPhoneNumber.py 中最后4個(gè) print() 函數(shù)調(diào)用:
message=‘Call me at 415-555-1011 tomorrow.415-555-9999 is my office.’
for i in range(len(message)):
chunk=message[i:i+12]
if isPhoneNumber(chunk):
print(‘Phone number found:’ + chunk)
print(‘Done’)
Phone number found:415-555-1011
Phone number found:415-555-9999
Done
該程序運(yùn)行時(shí),輸出看起來像這樣:
在 for 循環(huán)的每一次迭代中, 取自 message 的一段新的12個(gè)字符被賦給變量 chunk() 。 例如,在第一次迭代, i 是 0 , chunk 被賦值為 message[0:12] (即字符串’Call me at 4’)。在下一次迭代, i 是1, chunk 被賦值為 message[1:13] (字符串 ‘a(chǎn)ll me at 41’ )。
將 chunk 傳遞給 isPhoneNumber(), 看看它是否符合電話號碼的模式。如果符合, 就打印出這段文本。
繼續(xù)遍歷 message ,最終 chunk 中的12個(gè)字符會是一個(gè)電話號碼。 該循環(huán)遍歷了整個(gè)字符串,測試了每一段12個(gè)字符, 打印出所有滿足 isPhoneNumber() 的 chunk 。 當(dāng)我們遍歷完 message ,就打印出 Done 。
在這個(gè)例子中,雖然 message 中的字符串很短, 但它也可能包含上百萬個(gè)字符,程序運(yùn)行仍然不需要一秒鐘。 使用正則表達(dá)式查找電話號碼的類似程序, 運(yùn)行也不會超過一秒鐘,但用正則表達(dá)式編寫這類程序會快得多。
1.2. 用正則表達(dá)式查找文本模式
如果你想在有分機(jī)的電話號碼中快速查找電話號,例如415-555-4242 x99,該怎么辦呢?
正則表達(dá)式,又稱規(guī)則表達(dá)式,英語簡稱為 regex,是文本模式的描述方法,能幫助你方便的檢查一個(gè)字符串是否與某種模式匹配。
例如,\d 是一個(gè)正則表達(dá)式,表示一位數(shù)字字符, 即任何一位0到9的數(shù)字。 Python 使用正則表達(dá)式 \d\d\d-\d\d\d-\d\d\d\d , 來匹配前面 isPhoneNumber()函數(shù)匹配的同樣文本: 3個(gè)數(shù)字、一個(gè)短橫線、3個(gè)數(shù)字、一個(gè)短橫線、4個(gè)數(shù)字。 所有其他字符串都不能匹配 \d\d\d-\d\d\d-\d\d\d\d 正則表達(dá)式。
但正則表達(dá)式可以復(fù)雜得多。例如, 在一個(gè)模式后加上花括號包圍的3 ({3}), 就是說,“匹配這個(gè)模式3次”。 所以較短的正則表達(dá)式 \d{3}-\d{3}-\d{4} , 也匹配正確的電話號碼格式。
1.2.1. 創(chuàng)建正則表達(dá)式對象
Python 中所有正則表達(dá)式的函數(shù)都在 re 模塊中。在交互式環(huán)境中輸入以下代碼,導(dǎo)入該模塊:
import re
re 模塊的 compile() 函數(shù)根據(jù)一個(gè)模式字符串和可選的標(biāo)志參數(shù)生成一個(gè)正則表達(dá)式對象。 該對象擁有一系列方法用于正則表達(dá)式匹配和替換。 例如,向 re.compile() 傳入一個(gè)字符串值表示正則表達(dá)式, 它將返回一個(gè) Regex 模式對象(簡稱為 Regex 對象)。
要?jiǎng)?chuàng)建一個(gè) Regex 對象來匹配電話號碼模式, 就在交互式環(huán)境中輸入以下代碼。
regobj = re.compile(‘\d\d\d-\d\d\d-\d\d\d\d’)
現(xiàn)在 regobj 變量包含了一個(gè) Regex 對象。
1.2.2. 匹配 Regex 對象
Regex 對象的 search() 方法查找傳入的字符串,尋找該正則表達(dá)式的所有匹配。 如果字符串中沒有找到該正則表達(dá)式模式,search() 方法將返回None 。 如果找到了該模式,search() 方法將返回一個(gè) Match 對象。 Match 對象有一個(gè) group() 方法,它返回被查找字符串中實(shí)際匹配的文本。 例如,在交互式環(huán)境中輸入以下代碼:
mo = regobj.search(‘My number is 415-555-4242.’)
print('Phone number found: ’ + mo.group())
Phone number found: 415-555-4242
變量名 mo 是一個(gè)通用的名稱,用于 Match 對象。
這里,我們將期待的模式傳遞給 re.compile() ,并將得到的 Regex 對象保存在 phoneNumRegex 中。 然后我們在 phoneNumRegex 上調(diào)用 search() ,向它傳入想查找的字符串。查找的結(jié)果保存在變量 mo 中。 在這個(gè)例子里,我們知道模式會在這個(gè)字符串中找到,所以我們知道會返回一個(gè) Match 對象。 知道 mo 包含一個(gè) Match 對象,而不是空值 None ,我們就可以在 mo 變量上調(diào)用 group() ,返回匹配的結(jié)果。 將 mo.group() 寫在打印語句中,顯示出完整的匹配,即 415-555-4242 。
如果對象沒找到,可以進(jìn)行判斷:
mo2 = regobj.search(‘my number is 41555433.’)
print(‘got’) if mo2 else print(‘not got’)
not got
向 re.compile() 傳遞原始字符串
回憶一下, Python 中轉(zhuǎn)義字符使用倒斜杠()。 字符串 V 表示一個(gè)換行字符, 而不是倒斜杠加上一個(gè)小寫的 n 。 你需要輸入轉(zhuǎn)義字符 \ ,才能打印出一個(gè)倒斜杠。 所以 ‘\n’ 表示一個(gè)倒斜杠加上一個(gè)小寫的 n 。 但是,通過在字符串的第一個(gè)引號之前加上 r , 可以將該字符串標(biāo)記為原始字符串,它不包括轉(zhuǎn)義字符。
因?yàn)檎齽t表達(dá)式常常使用倒斜杠, 向 re.compile() 函數(shù)傳入原始字符串就很方便, 而不是輸入額外得到斜杠。輸入 r’\d\d\d-\d\d\d-\d\d\d\d’ , 比輸入 ‘\d\d\d-\d\d\d-\d\d\d\d’ 要容易得多。
1.2.3. 正則表達(dá)式匹配復(fù)習(xí)
雖然在 Python中使用正則表達(dá)式有幾個(gè)步驟, 但每一步都相當(dāng)簡單。
用 import re 導(dǎo)入正則表達(dá)式模塊。
用 re.compile() 函數(shù)創(chuàng)建一個(gè) Regex 對象(記得使用原始字符串)。
向 Regex 對象的 search() 方法傳入想查找的字符串。它返回一個(gè) Match 對象。
調(diào)用 Match 對象的 group() 方法,返回實(shí)際匹配文本的字符串。
下面主要介紹Python中常用的正則表達(dá)式處理函數(shù)。
1.2.4. re.match函數(shù)
re.match() 嘗試從字符串的起始位置匹配一個(gè)模式,如果不是起始位置匹配成功的話, match() 就返回 None 。
函數(shù)語法:
re.match(pattern, string, flags=0)
函數(shù)參數(shù)說明:
pattern 匹配的正則表達(dá)式
string 要匹配的字符串。
flags 標(biāo)志位,用于控制正則表達(dá)式的匹配方式,如:是否區(qū)分大小寫,多行匹配等等。
我們可以使用 group(num) 或 groups() 匹配對象函數(shù)來獲取匹配表達(dá)式。
group(num=0) 匹配的整個(gè)表達(dá)式的字符串, group() 可以一次輸入多個(gè)組號,在這種情況下它將返回一個(gè)包含那些組所對應(yīng)值的元組。
groups() 返回一個(gè)包含所有小組字符串的元組,從 1 到 所含的小組號。
實(shí)例 1:
在起始位置匹配
print(re.match(‘www’, ‘www.runoob.com’).span())
(0, 3)
不在起始位置匹配
print(re.match(‘com’, ‘www.runoob.com’))
None
1.2.5. re.search方法
re.search 掃描整個(gè)字符串并返回第一個(gè)成功的匹配。
函數(shù)語法:
re.search(pattern, string, flags=0)
函數(shù)參數(shù)說明:
參數(shù) | 描述
pattern | 匹配的正則表達(dá)式
string | 要匹配的字符串。
flags | 標(biāo)志位,用于控制正則表達(dá)式的匹配方式,如:是否區(qū)分大小寫,多行匹配等等。
匹配成功 re.search 方法返回一個(gè)匹配的對象,否則返回None。
我們可以使用 group(num) 或 groups() 匹配對象函數(shù)來獲取匹配表達(dá)式。
匹配對象方法 | 描述
group(num=0) |匹配的整個(gè)表達(dá)式的字符串,group() 可以一次輸入多個(gè)組號,在這種情況下它將返回一個(gè)包含那些組所對應(yīng)值的元組。
groups() | 返回一個(gè)包含所有小組字符串的元組,從 1 到 所含的小組號。
實(shí)例 1:
在起始位置匹配
print(re.search(‘www’, ‘www.runoob.com’).span())
(0, 3)
不在起始位置匹配
print(re.search(‘com’, ‘www.runoob.com’).span())
(11, 14)
實(shí)例 2:
import re
line = “Cats are smarter than dogs”;
searchObj = re.search(‘(.) are (.?) .*’, line, re.M|re.I)
if searchObj:
print ("searchObj.group() : ", searchObj.group())
print ("searchObj.group(1) : ", searchObj.group(1))
print ("searchObj.group(2) : ", searchObj.group(2))
else:
print (“Nothing found!!”)
searchObj.group() : Cats are smarter than dogs
searchObj.group(1) : Cats
searchObj.group(2) : smarter
instr = ‘/home/bk/book-rst/doculet/sphinx-tutorial/runoob-src/pt01_language_eb00kh’
re_book = re.compile(‘eb\d\d…’)
uu = re_book.search(instr)
uu.span()
(67, 73)
dir(uu)
[‘class’,
‘class_getitem’,
‘copy’,
‘deepcopy’,
‘delattr’,
‘dir’,
‘doc’,
‘eq’,
‘format’,
‘ge’,
‘getattribute’,
‘getitem’,
‘getstate’,
‘gt’,
‘hash’,
‘init’,
‘init_subclass’,
‘le’,
‘lt’,
‘module’,
‘ne’,
‘new’,
‘reduce’,
‘reduce_ex’,
‘repr’,
‘setattr’,
‘sizeof’,
‘str’,
‘subclasshook’,
‘end’,
‘endpos’,
‘expand’,
‘group’,
‘groupdict’,
‘groups’,
‘lastgroup’,
‘lastindex’,
‘pos’,
‘re’,
‘regs’,
‘span’,
‘start’,
‘string’]
uu.group()
‘eb00kh’
1.2.6. re.match 與 re.search 的區(qū)別
re.match 只匹配字符串的開始,如果字符串開始不符合正則表達(dá)式,則匹配失敗,函數(shù)返回 None ; 而 re.search 匹配整個(gè)字符串,直到找到一個(gè)匹配。
實(shí)例:
import re
line = “Cats are smarter than dogs”;
matchObj = re.match( ‘dogs’, line, re.M|re.I)
if matchObj:
print ("match --> matchObj.group() : ", matchObj.group())
else:
print (“No match!!”)matchObj = re.search( r’dogs’, line, re.M|re.I)
if matchObj:
print ("search --> matchObj.group() : ", matchObj.group())
else:
print (“No match!!”)
No match!!
search --> matchObj.group() : dogs
1.2.7. 檢索和替換
Python 的re模塊提供了re.sub用于替換字符串中的匹配項(xiàng)。
語法:
re.sub(pattern, repl, string, count=0)
參數(shù):
pattern : 正則中的模式字符串。
repl : 替換的字符串,也可為一個(gè)函數(shù)。
string : 要被查找替換的原始字符串。
count : 模式匹配后替換的最大次數(shù),默認(rèn) 0 表示替換所有的匹配。
實(shí)例:
#!/usr/bin/python3
import rephone = “2004-959-559 # 這是一個(gè)電話號碼”
刪除注釋
num = re.sub(‘#.*$’, “”, phone)
print ("電話號碼 : ", num)移除非數(shù)字的內(nèi)容
num = re.sub(‘\D’, “”, phone)
print ("電話號碼 : ", num)
電話號碼 : 2004-959-559
電話號碼 : 2004959559
1.2.8. repl 參數(shù)是一個(gè)函數(shù)
以下實(shí)例中將字符串中的匹配的數(shù)字乘于 2:
#!/usr/bin/python
import re
將匹配的數(shù)字乘于 2
def double(matched):
value = int(matched.group(‘value’))
return str(value * 2)s = ‘A23G4HFD567’
print(re.sub(‘(?P\d+)’, double, s))
A46G8HFD1134
1.2.9. 正則表達(dá)式修飾符 - 可選標(biāo)志
正則表達(dá)式可以包含一些可選標(biāo)志修飾符來控制匹配的模式。修飾符被指定為一個(gè)可選的標(biāo)志。多個(gè)標(biāo)志可以通過按位 OR(|) 它們來指定。如 re.I | re.M 被設(shè)置成 I 和 M 標(biāo)志:
修飾符 | 描述
re.I 使匹配對大小寫不敏感
re.L 做本地化識別(locale-aware)匹配
re.M 多行匹配,影響 ^ 和 $
re.S 使 . 匹配包括換行在內(nèi)的所有字符
re.U 根據(jù)Unicode字符集解析字符。這個(gè)標(biāo)志影響 \w , \W , \b , \B .
re.X 該標(biāo)志通過給予你更靈活的格式以便你將正則表達(dá)式寫得更易于理解。
1.2.10. 正則表達(dá)式模式
模式字符串使用特殊的語法來表示一個(gè)正則表達(dá)式:
字母和數(shù)字表示他們自身。一個(gè)正則表達(dá)式模式中的字母和數(shù)字匹配同樣的字符串。
多數(shù)字母和數(shù)字前加一個(gè)反斜杠時(shí)會擁有不同的含義。
標(biāo)點(diǎn)符號只有被轉(zhuǎn)義時(shí)才匹配自身,否則它們表示特殊的含義。
反斜杠本身需要使用反斜杠轉(zhuǎn)義。
由于正則表達(dá)式通常都包含反斜杠,所以你最好使用原始字符串來表示它們。 模式元素(如 r’/t’ ,等價(jià)于’//t’ )匹配相應(yīng)的特殊字符。
下表列出了正則表達(dá)式模式語法中的特殊元素。如果你使用模式的同時(shí)提供了可選的標(biāo)志參數(shù),某些模式元素的含義會改變。
模式 | 描述
^ 匹配字符串的開頭
$ 匹配字符串的末尾。
. 匹配任意字符,除了換行符,當(dāng)re.DOTALL標(biāo)記被指定時(shí),則可以匹配包括換行符的任意字符。
[…] 用來表示一組字符,單獨(dú)列出: [amk] 匹配 ‘a(chǎn)’,‘m’或’k’
[^…] 不在[]中的字符: [^abc] 匹配除了a,b,c之外的字符。
re* 匹配0個(gè)或多個(gè)的表達(dá)式。
re+ 匹配1個(gè)或多個(gè)的表達(dá)式。
re? 匹配0個(gè)或1個(gè)由前面的正則表達(dá)式定義的片段,非貪婪方式
re{ n}
re{ n,} 精確匹配n個(gè)前面表達(dá)式。
re{ n, m} 匹配 n 到 m 次由前面的正則表達(dá)式定義的片段,貪婪方式
a|b 匹配a或b
(re) G匹配括號內(nèi)的表達(dá)式,也表示一個(gè)組
(?imx) 正則表達(dá)式包含三種可選標(biāo)志:i, m, 或 x 。只影響括號中的區(qū)域。
(?-imx) 正則表達(dá)式關(guān)閉 i, m, 或 x 可選標(biāo)志。只影響括號中的區(qū)域。
(?: re) 類似 (…), 但是不表示一個(gè)組
(?imx:re) 在括號中使用i, m, 或 x 可選標(biāo)志
(?-imx: re) 在括號中不使用i, m, 或 x 可選標(biāo)志
(?#…) 注釋.
(?= re) 前向肯定界定符。如果所含正則表達(dá)式,以 … 表示,在當(dāng)前位置成功匹配時(shí)成功,否則失敗。但一旦所含表達(dá)式已經(jīng)嘗試,匹配引擎根本沒有提高;模式的剩余部分還要嘗試界定符的右邊。
(?! re) 前向否定界定符。與肯定界定符相反;當(dāng)所含表達(dá)式不能在字符串當(dāng)前位置匹配時(shí)成功
(?> re) 匹配的獨(dú)立模式,省去回溯。
\w 匹配字母數(shù)字
\W 匹配非字母數(shù)字
\s 匹配任意空白字符,等價(jià)于 [\t\n\r\f] .
\S 匹配任意非空字符
\d 匹配任意數(shù)字,等價(jià)于 [0-9] .
\D 匹配任意非數(shù)字
\A 匹配字符串開始
\Z 匹配字符串結(jié)束,如果是存在換行,只匹配到換行前的結(jié)束字符串。c
\z 匹配字符串結(jié)束
\G 匹配最后匹配完成的位置。
\b 匹配一個(gè)單詞邊界,也就是指單詞和空格間的位置。例如, er\b 可以匹配 never 中的 er , 但不能匹配 verb 中的 er 。
\B 匹配非單詞邊界?!甧r:raw-latex:B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。
\n, \t , 等. 匹配一個(gè)換行符。匹配一個(gè)制表符。等
\1…\9 匹配第n個(gè)分組的內(nèi)容。
\10 匹配第n個(gè)分組的內(nèi)容,如果它經(jīng)匹配。否則指的是八進(jìn)制字符碼的表達(dá)式。
正則表達(dá)式符號復(fù)習(xí)
本章介紹了許多表示法,所以這里快速復(fù)習(xí)一下學(xué)到的內(nèi)容:
? 匹配零次或一次前面的分組。
- 匹配零次或多次前面的分組。
+匹配一次或多次前面的分組。
{n}匹配 n 次前面的分組。
{n,} 匹配 n 次或更多前面的分組。
{,m} 匹配零次到 m 次前面的分組。
{n,m}匹配至少 n 次、至多 m 次前面的分組。
{n,m}? 或 *? 或 +? 對前面的分組進(jìn)行非貪心匹配。
^spam 意味著字符串必須以 spam 開始。
spam$意味著字符串必須以 spam 結(jié)束。
.匹配所有字符,換行符除外。
\d 、 \w和\s 分別匹配數(shù)字、單詞和空格。
\D、\W和 \S分別匹配出數(shù)字、單詞和空格外的所有字符。
[abc] 匹配方括號內(nèi)的任意字符(諸如a、b或c)。文章來源:http://www.zghlxwxcb.cn/news/detail-732194.html
[^abc] 匹配不在方括號內(nèi)的任意字符。文章來源地址http://www.zghlxwxcb.cn/news/detail-732194.html
到了這里,關(guān)于【Python】Python 模式匹配與正則表達(dá)式的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!