1. 程式人生 > >第7章 模式匹配與正則表示式

第7章 模式匹配與正則表示式

  • python 中所有的正則表示式函式都在re模組中。
  • 向re.complie()傳入一個字串值,來表示正則表示式,它將返回一個Regex模式物件。
  • Regex 物件的serch()方法查詢傳入的字串,尋找該正則表示式的所有匹配。如果字串中沒有找到該正則表示式模式,search()方法將返回None。如果找到了該模式,search()方法將返回一個Match物件。Match物件有一個group()方法,它返回被查詢字串中實際匹配的文字。

正則表示式匹配複習
- 1.用import re匯入正則表示式模組
- 2.用re.compile()函式建立一個Regex物件(記得使用原始字串)
- 3.向Regex物件的search()方法傳入想查詢的字串。它返回一個Match物件。
- 4.呼叫Match物件的group()方法,返回實際匹配文字字串。

利用括號分組

假定想要將區號從電話號碼中分離。新增括號將在正則表示式中建立“分組”:(\d\d\d)-(\d\d\d-\d\d\d)。然後使用group()匹配物件方法,從一個分組中獲取匹配的文字。
正則表示式字串中的第一對括號是第1組。第二對括號是第2組。向group()匹配物件方法傳入整數1或者2,就可以匹配文字的不同部分。向group()方法中傳入0或者不傳入引數,將返回整個匹配的文字。

>>> import re
>>> phoneNumberRegex = re.compile(r'(\d\d\d)-(\d\d\d-\d\d\d)')
>>> 
mo = phoneNumberRegex.search('my number is 415-555-4242.') >>> mo.group(1) '415' >>> mo.groups() ('415', '555-424')
  • 如果要一次獲取所有分組,那麼使用groups()方法
  • 字元“|”稱為管道。希望匹配許多表達式中的一個時,就可以使用它。
  • 如果查詢的字串中,兩個或者多個都出現時,第一次出現的匹配文字將作為Match物件返回。
>>> heroRegex = re.compile(r'batman|tina fey')
>>> 
mo1 = heroRegex.search('batman and tina fey') >>> mo1.group() 'batman' >>> mo1 = heroRegex.search(' tina fey and batman ') >>> mo1.group() 'tina fey'

可以試用管道來匹配多個模式中的一個,作為正則表示式的一部分。

>>> batRegex = re.complie(r'bat(man|mobile|copter|bat)')
>>> batRegex = re.compile(r'bat(man|mobile|copter|bat)')
>>> mo = batRegex.search('batmobile lost a wheel')
>>> mo.group()
'batmobile'
>>> mo.group(1)
'mobile'
>>> 

用問號實現可選匹配

有時候,向匹配的模式是可選的。就是說,不論這段文字在不在,正則表示式都會認為匹配。字元?表明它前面的分組在這個模式中是可選的。

>>> phoneNumberRegex = re.compile(r'(\d\d\d-)?\d\d\d-\d\d\d')
>>> mo = phoneNumberRegex.search('my number is 415-555-4242.')
>>> mo.group()
'415-555-424'
>>> mo = phoneNumberRegex.search('my number is 555-4242.')
>>> mo.group()
'555-424'

正則表示式中(\d\d\d-)?部分表明,模式(\d\d\d-)是可選的。也就是匹配這個問號之前的分組零次或一次

用星號匹配零次或多次

“*”星號之前的分組,可以在文字中出現任意次。

用加號匹配一次或多次

“+”加號之前的分割槽,至少在文字中出現一次

用花括號匹配特定的次數

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

貪心匹配和非貪心匹配

python的正則表示式模式的是“貪心”的,這表示在有二意的情況下,他們會盡可能的匹配最長的字串。花括號的“非貪心”版本匹配儘可能最短的字串,即在結束的花括號後跟著一個問號。

問號在正則表示式中有兩種含義:宣告非貪心匹配或表示可選的分組。

findall()方法

除了search方法外,Regex物件也有一個findall()方法。search()將返回一個Match物件,包含被查詢字串中的“第一次”匹配的文字,而findall()方法將返回一組字串,包含被查詢字串中的所有匹配。
- 如果呼叫在一個沒有分組的正則表示式上,例如\d\d\d-\d\d\d-\d\d\d\d,方法findall()將返回一個匹配字串的列表,例如[‘414-555-9999’,’212-555-0000’]
- 如果呼叫在一個有分組的正則表示式上,例如(\d\d\d)-(\d\d\d)-(\d\d\d\d),方法findall()將返回一個字串的元組列表(每一個分組對應一個字串),例如[(‘415’,’555’,’1121’),(‘212’,’555,’0000)]

字元分類

縮寫字元分類 表示
\d 0到9的任何數字
\D 除0到9的數字以外的任何字元
\w 任何字母、數字或下劃線字元(可以認為是匹配“單詞”字元
\W 除字母、數字和下劃線以外的任何字元
\s 空格、製表符或換行符(可以認為是匹配“空白”字元)
\S 除空格、製表符和換行符以外的任何字元

建立自己的字元分類

  • 用方括號定義自己的字元分類。例如,字元分類[adiouAEIOU]將匹配所有原因字元,不論大小寫。
  • 也可以石永紅短橫線表示字母或者數字的範圍。例如[0-5]只匹配數字0到5
  • 在方括號內,普通的正則表示式符號不會被解釋。
  • 通過在字元分類的左方括號後加上一個插入字元(^),就可以得到“非字元類”。非字元類將匹配不在這個字元類中的所有字元。
#匹配所有非母音字元
>>> consonantRegex = re.compile(r'[^aeiouAEIOU]')
>>> consonantRegex.findall('RoboCop eats baby food. BABY FOOD.')
['R', 'b', 'C', 'p', ' ', 't', 's', ' ', 'b', 'b', 'y', ' ', 'f', 'd', '.', ' ', 'B', 'B', 'Y', ' ', 'F', 'D', '.']

插入字元和美元字元

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

>>> beginsWithHello = re.compile(r'^Hello')
>>> beginsWithHello.search('Hello world!')
<_sre.SRE_Match object; span=(0, 5), match='Hello'>
>>> hh=beginsWithHello.search('Hello world!')
>>> hh.group()
'Hello'
>>> endsWithNumber = re.compile(r'\d$')
>>> ss=endsWithNumber.search('Your number is 42')
>>> ss.group()
'2'
>>> wholeStringIsNum = re.compile(r'^\d+$')
>>> rr=wholeStringIsNum.search('1234567890')
>>> rr.group()
'1234567890'

通配字元

在正則表示式中,.(句點)字元稱為“萬用字元”。它匹配除了換行之外的所有
字元。

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

句點字元只匹配一個字元,這就是為什麼在前面的例子中,對於文字flat,只匹配 lat。

用點-星匹配所有字元

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

>>> nameRegex = re.compile(r'First Name: (.*) Last Name: (.*)')
>>> mo = nameRegex.search('First Name: Al Last Name: Sweigart')
>>> mo.group(1)
'Al'
>>> mo.group(2)
'Sweigart'

用句點字元匹配換行

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

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

正則表示式符號複習

  • ?匹配零次或一次前面的分組。
  • *匹配零次或多次前面的分組。
  • +匹配一次或多次前面的分組。
  • {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]匹配不在方括號內的任意字元。

不區分大小寫的匹

向re.comlile()傳入re.IGNORECASE或re.I,作為第二個引數

用sub()方法替換字串

Regex物件的sub()方法需要傳入兩個引數。第一個引數是字串,用於取代發現的匹配。第二個引數是一個字串,即正則表示式。sub()方法返回替換完成後的字串。

>>> namesRegex = re.compile(r'Agent \w+')
>>> namesRegex.sub('CENSORED', 'Agent Alice gave the secret documents to Agent Bob.')
'CENSORED gave the secret documents to CENSORED.'

有時候,你可能需要使用匹配的文字本身,作為替換的一部分。在 sub()的第一個引數中,可以輸入\1、\2、\3……。表示“在替換中輸入分組1、2、3……的文字”。

例如,假定想要隱去密探的姓名,只顯示他們姓名的第一個字母。要做到這一點,可以使用正則表示式 Agent (\w)\w*,傳入 r’\1****’作 sub()的第一個引數。字串中的\1 將由分組 1匹配的文字所替代,也就是正則表示式的(\w)分組。

>>> agentNamesRegex = re.compile(r'Agent (\w)\w*')
>>> agentNamesRegex.sub(r'\1****', 'Agent Alice told Agent Carol that Agent
Eve knew Agent Bob was a double agent.')
A**** told C**** that E**** knew B**** was a double agent.'

如果要匹配的文字模式很簡單,正則表示式就很好。但匹配複雜的文字模式,可能需要長的、費解的正則表示式。你可以告訴re.compile(),忽略正則表示式字串中的空白符和註釋,從而緩解這一點。要實現這種詳細模式,可以向 re.compile()
傳入變數 re.VERBOSE,作為第二個引數。

專案 電話號碼和 E-mail 地址提取程式
假設你有一個無聊的任務,要在一篇長的網頁或文章中,找出所有電話號碼和郵件地址。如果手動翻頁,可能需要查詢很長時間。如果有一個程式,可以在剪貼簿的文字中查詢電話號碼和 E-mail地址,那你就只要按一下Ctrl-A選擇所有文字,按下 Ctrl-C 將它複製到剪貼簿,然後執行你的程式。它會用找到的電話號碼和 E-mail地址,替換掉剪貼簿中的文字。

import re
import pyperclip

phoneRegex = re.compile(r'''(
(\d{3}|\(\d{3}\))?
(\s\|-|\.)?
(\d{3})
(\s|-|\.)
(\d{4})
(\\s*(ext|x|ext.)\s*(\d{2,5}))?
)''', re.VERBOSE)

emailRegex = re.compile(r'''
([a-zA-Z0-9._%+-]+
@
[a-zA-Z0-9.-]+
(\.[a-zA-Z]{2,4})
)''', re.VERBOSE)

text = str(pyperclip.paste())
matches = []
for groups in phoneRegex.findall(text):
    phoneNum = '-'.join([groups[1],groups[3],groups[5]])
    if groups[8] != '':
        phoneNum += 'x' + groups[8]
    matches.append(phoneNum)
for groups in emailRegex.findall(text):
    matches.append(groups[0])

if len(matches) >0:
    pyperclip.copy('\n'.join(matches))
    print('Copied to clipboard :')
    print('\n'.join(matches))
else:
    print('No phone numbers or email addresses found.')