1. 模式匹配與正則表達式筆記(第七章)
正則表達式,又稱規則表達式。(英語:Regular Expression,在代碼中常簡寫爲regex、regexp或RE),計算機科學的一個概念。正則表達式通常被用來檢索、替換那些符合某個模式(規則)的文本。
1.1 正則表達式匹配步驟
雖然在Python 中使用正則表達式有幾個步驟,但每一步都相當簡單。
- 用import re 導入正則表達式模塊。
- 用re.compile()函數創建一個Regex 對象(記得使用原始字符串)。
- 向Regex 對象的search()方法傳入想查找的字符串。它返回一個Match 對象。
- 調用Match 對象的group()方法,返回實際匹配文本的字符。
# 用import re 導入正則表達式模塊
import re
# 創建了一個Regex對象
phoneNumRegex = re.compile(r'\d\d\d-\d\d\d-\d\d\d\d')
# 匹配字符串
mo = phoneNumRegex.search('My number is 415-555-4242.')
# group()方法返回實際匹配的字符串
print('phone num found is: ' + mo.group())
phone num found is: 415-555-4242
1.2 正則表達式匹配更多模式
- 利用括號分組
添加括號將在正則表達式中創建“分組”(\d\d\d)-(\d\d\d-\d\d\d\d)。然後可以使用group()匹配對象方法,從一個分組中獲取匹配的文本。 - 用管道匹配多個分組
字符|稱爲“管道”。希望匹配許多表達式中的一個時,就可以使用它。例如,正則表達式r’Batman|Tina Fey’將匹配’Batman’或’Tina Fey’。 - 用問號實現可選匹配
有時候,想匹配的模式是可選的。就是說,不論這段文本在不在,正則表達式都會認爲匹配。字符?表明它前面的分組在這個模式中是可選的。也可以理解爲匹配這個問號之前的分組零次或一次。 - 用星號匹配零次或多次
*(稱爲星號)意味着“匹配零次或多次”,即星號之前的分組,可以在文本中出現任意次。它可以完全不存在,或一次又一次地重複。 - 用加號匹配一次或多次
加號+前面的分組必須“至少出現一次 - 用花括號匹配特定次數
- 如果想要一個分組重複特定次數,就在正則表達式中該分組的後面,跟上花括號包圍的數字。例如,正則表達式(Ha){3}將匹配字符串’HaHaHa’。
- 除了一個數字,還可以指定一個範圍,即在花括號中寫下一個最小值、一個逗號和一個最大值。例如,正則表達式(Ha){1,2}將匹配 ‘Ha’ 和 ‘HaHa’。
- 也可以不寫花括號中的第一個或第二個數字,不限定最小值或最大值。例如,(Ha){3,}將匹配3 次或更多次實例。
- 用花括號和問號匹配非貪心模式
Python 的正則表達式默認是“貪心”的,這表示在有二義的情況下,它們會儘可能匹配最長的字符串。花括號的“非貪心”版本匹配儘可能最短的字符串,即在
結束的花括號後跟着一個問號。
1.3 findall()方法
search()將返回一個Match對象,包含被查找字符串中的“第一次”匹配的文本,而findall()方法將返回一組字符串,包含被查找字符串中的所有匹配。
import re
phoneNumRegex = re.compile(r'\d\d\d-\d\d\d-\d\d\d\d')
# search返回第一次匹配
mo = phoneNumRegex.search('Cell: 415-555-9999 Work: 212-555-0000')
mo.group()
'415-555-9999'
# findall返回所有結果
phoneNumRegex = re.compile(r'\d\d\d-\d\d\d-\d\d\d\d')
phoneNumRegex.findall('Cell: 415-555-9999 Work: 212-555-0000')
['415-555-9999', '212-555-0000']
1.4 字符分類
縮寫字符分類 | 表示 |
---|---|
\d | 0 到9 的任何數字 |
\D | 除0 到9 的數字以外的任何字符 |
\w | 任何字母、數字或下劃線字符(可以認爲是匹配“單詞”字符) |
\W | 除字母、數字和下劃線以外的任何字符 |
\s | 空格、製表符或換行符(可以認爲是匹配“空白”字符) |
\S | 除空格、製表符和換行符以外的任何字符 |
1.5 建立自己的字符分類
- 有時候你想匹配一組字符,但縮寫的字符分類(\d、\w、\s 等)太寬泛。你可
以用方括號定義自己的字符分類。例如,字符分類[aeiouAEIOU]將匹配所有元音字
符,不論大小寫。
import re
vowelRegex = re.compile(r'[aeiouAEIOU]')
vowelRegex.findall('RoboCop eats baby food. BABY FOOD.')
['o', 'o', 'o', 'e', 'a', 'a', 'o', 'o', 'A', 'O', 'O']
- 可以使用短橫表示字母或數字的範圍。例如,字符分類[a-zA-Z0-9]將匹配所有小寫字母、大寫字母和數字。
import re
vowelRegex = re.compile(r'[a-zA-Z0-9]')
vowelRegex.findall('RoboCop eats baby food. BABY FOOD.')
['R',
'o',
'b',
'o',
'C',
'o',
'p',
'e',
'a',
't',
's',
'b',
'a',
'b',
'y',
'f',
'o',
'o',
'd',
'B',
'A',
'B',
'Y',
'F',
'O',
'O',
'D']
- 通過在字符分類的左方括號後加上一個插入字符(^),就可以得到“非字符類”。非字符類將匹配不在這個字符類中的所有字符。
import re
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',
'.']
- 插入字符和美元字符
正則表達式的開始處使用插入符號(^),表明匹配必須發生在被查找文本開始處。
正則表達式的末尾加上美元符號,表示該字符串必須以這個正則表達式的模式結束。可以同時使用^和$,表明整個字符串必須匹配該模式。
1.6 通配字符
- .(句點)字符稱爲“通配符”。它匹配除了換行之外的所有字符。
- 用點-星(.*)表示“任意文本”。
- 通過傳入re.DOTALL 作爲re.compile()的第二個參數,可以讓句點字符匹配所有字符,包括換行字符。
1.7 正則表達式符號總結
1.8 sub() 替換字符串
Regex對象的sub()方法需要傳入兩個參數。第一個參數是一個字符串,用於取代發現的匹配。第二個參數是一個字符串,即正則表達式。sub()方法返回替換完成後的字符串。
import re
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.'
1.9 compile的第二個參數
- re.IGNORECASE 忽視大小寫
- re.VERBOSE 編寫註釋
- re.DOTALL 句點表示所有字符(沒有這個則表示除換行符外的所有字符)
2. 項目
2.1 電話號碼和E-mail 地址提取程序
功能:在剪貼板的文本中查找電話號碼和E-mail 地址,按一下Ctrl-A 選擇所有文本,按下Ctrl-C 將它複製到剪貼板,然後運行程序。找到電話號碼和E-mail地址,替換掉剪貼板中的文本。
文本例子地址:http://www.nostarch.com/contactus
主要步驟:
第1步:爲電話號碼創建一個正則表達式
第2步:爲E-mail 地址創建一個正則表達式
第3步:在剪貼板文本中找到所有匹配
第4步:所有匹配連接成一個字符串,複製到剪貼板
#! python3
# Finds phone numbers and email addresses on the clipboard.
# text:
'''
Contact Us
No Starch Press, Inc.
245 8th Street
San Francisco, CA 94103 USA
Phone: 800.420.7240 or +1 415.863.9900 (9 a.m. to 5 p.m., M-F, PST)
Fax: +1 415.863.9950
Reach Us by Email
General inquiries: [email protected]
Media requests: [email protected]
Academic requests: [email protected] (Please see this page for academic review requests)
Help with your order: [email protected]
Reach Us on Social Media
Twitter
Facebook
Instagram
Pinterest
100.420.7240 x 123
'''
import pyperclip
import re
# 爲電話號碼創建一個正則表達式
# 電話號碼 phone
phoneRegex = re.compile(r'''(
(\d{3}|\(\d{3}\))? # area code
(\s|-|\.)? # separator
(\d{3}) # first 3 digits
(\s|-|\.) # separator
(\d{4}) # last 4 digits
(\s*(ext|x|ext.)\s*(\d{2,5}))? # extension
)''', re.VERBOSE)
# 爲E-mail 地址創建一個正則表達式
# Create email regex. 郵件
emailRegex = re.compile(r'''(
[a-zA-Z0-9._%+-]+ # username
@ # @ symbol
[a-zA-Z0-9.-]+ # domain name
(\.[a-zA-Z]{2,4}) # dot-something
)''', re.VERBOSE)
# TODO: Create email regex.
# TODO: Find matches in clipboard text.
# TODO: Copy results to the clipboard.
# Find matches in clipboard text.
# 獲得剪切板文件
text = str(pyperclip.paste())
matches = []
a = phoneRegex.findall(text)
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])
# Copy results to the clipboard.
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.')
Copied to clipboard:
800-420-7240
415-863-9900
415-863-9950
100-420-7240 x123
[email protected]
[email protected]
[email protected]
[email protected]
2.2 強口令檢測
寫一個函數,它使用正則表達式,確保傳入的口令字符串是強口令。強口令的定義是:長度不少於8 個字符,同時包含大寫和小寫字符,至少有一位數字。
import re
import pyperclip
def detect(text):
if len(text)<8:
return False
test1=re.compile(r'([0-9])')
result1=test1.search(text)
if result1==None:
return False
test2=re.compile(r'([A-Z])')
result2=test2.search(text)
if result2==None:
return False
test3=re.compile(r'([a-z])')
result3=test3.search(text)
if result3==None:
return False
return True
text='uusssZmi0546'
status=detect(text)
print(status)
True
2.3 strip()的正則表達式版本
寫一個函數,它接受一個字符串,做的事情和strip()字符串方法一樣。如果只傳入了要去除的字符串,沒有其他參數,那麼就從該字符串首尾去除空白字符。否
則,函數第二個參數指定的字符將從該字符串中去除。
import re
def my_strip(text,param=''):
if len(param)<1:
param='\s'
params_begin= r'^[{}]*'.format(param)
params_end= r'[{}]*$'.format(param)
my_begin=re.compile(params_begin,re.I)
my_end=re.compile(params_end,re.I)
my=my_begin.sub('', text)
text=my_end.sub('', my)
return text
text='SpamSpamBaconSpamEggsSpamSpam'
param='SPAM'
text=my_strip(text,'SPAM')
print(text)
BaconSpamEgg