26 Python 中使用正則表示式
1. 正則表示式
1.1 簡介
正則表示式 (regular expression) 描述了一種字串匹配的模式 (pattern),例如:
- 模式 ab+c
- 可以匹配 abc、abbc、abbbc
-
- 代表前面的字元出現 1 次或者多次
- 模式 ab*c
- 可以匹配 ac、abc、abbc
- ? 代表前面的字元出現 0 次或者多次
- 模式 ab?c
- 可以匹配 ac、abc
- ? 代表前面的字元出現 0 次或者 1 次
它的用途包括:
- 檢查一個串是否含有某種子串
- 將匹配的子串替換
- 從某個串中取出符合某個條件的子串
1.2 普通字元
正則表示式是由普通字元(例如字元 a 到 z)以及特殊字元(稱為"元字元")組成的文字模式。模式描述在搜尋文字時要匹配的一個或多個字串。正則表示式作為一個模板,將某個字元模式與所搜尋的字串進行匹配。
普通字元包括沒有顯式指定為元字元的所有可列印和不可列印字元。這包括所有大寫和小寫字母、所有數字、所有標點符號和一些其他符號。
1.3 特殊字元
特殊字元是一些有特殊含義的字元,例如的 ab*c 中的 *,* 之前的字元是 b,* 表示匹配 0 個或者多個 字元 b。下表列出了正則表示式中的特殊字元:
特殊字元 | 描述 |
---|---|
\t | 製表符 |
\f | 換頁符 |
\n | 換行符 |
\r | 回車符 |
\s | 匹配任意空白字元,等價於 [\t\n\r\f] |
\S | 匹配任意非空字元 |
\d | 匹配任意數字,等價於 [0-9] |
\D | 匹配任意非數字 |
^ | 匹配字串的開頭 |
$ | 匹配字串的末尾 |
. | 匹配任意字元 |
\b | 匹配一個單詞邊界,在單詞的開頭或者末尾匹配xcd ef’ |
\B | 匹配非單詞邊界 |
[…] | 用來表示一組字元 |
[^…] | 不在[]中的字元 |
re* | 匹配 0 個或多個正則表示式 |
re+ | 匹配 1 個或多個正則表示式 |
re? | 匹配 0 個或 1 個正則表示式 |
re{n} | 匹配 n 個正則表示式 |
re{n,m} | 匹配 n 到 m 個正則表示式 |
a | b | 匹配 a或 b |
(re) | 對正則表示式分組並記住匹配的文字 |
2. 模組 re
2.1 簡介
Python 提供了 re 模組,提供正則表示式的模式匹配功能。在 re 模組中定義瞭如下常用函式:
函式 | 功能 |
---|---|
re.match(pattern, string, flags) | 從字串 string 的起始位置,查詢符合模式 pattern 的子串 |
re.search(pattern, string, flags) | 從字串 string 的任意位置,查詢符合模式 pattern 的子串 |
re.split(pattern, string) | 根據分隔符 pattern 將字串 string 分割 |
re.sub(pattern, replace, string) | 將字串中匹配模式 patter 的子串替換字串 replace |
2.2 正則表示式修飾符
正則表示式可以包含一些可選修飾符來控制匹配的模式。修飾符被指定為一個可選的標誌,多個標誌可以通過按位 OR(|) 它們來指定,如 re.I | re.M 被設定成 I 和 M 標誌。下表列舉了常用的正則表示式修飾符:
修飾符 | 描述 |
---|---|
re.I | 使匹配對大小寫不敏感 |
re.M | 多行匹配,影響 ^ 和 $ |
2.3 re.MatchObject
re.MatchObject 表示模式匹配的結果,該物件包含 3 個成員方法:
- start() 返回匹配開始的位置
- end() 返回匹配結束的位置
- span() 返回一個元組包含匹配 (開始,結束) 的位置
2.4 re.RegexObject
re.RegexObject 表示正則表示物件,該物件包含 2 個成員方法:
- match(string) | 從字串 string 的起始位置,查詢符合模式 pattern 的子串
- serach(string) | 從字串 string 的任意位置,查詢符合模式 pattern 的子串
3. 在字串查詢與模式匹配的字串
3.1 從字串的起始位置進行匹配
函式 re.match(pattern, string, flags = 0) 用於在字串查詢與模式匹配的字串:
- 從字串 string 的起始位置,查詢符合模式 pattern 的子串
- 如果匹配成功,則返回一個 re.MatchObject 物件
- 如果匹配失敗,則返回 None
- 引數 flags,用於控制正則表示式的匹配方式,如:是否區分大小寫,多行匹配等
函式的使用示例如下:
>>> import re
>>> matchObject = re.match('w+', 'www.imooc.com')
>>> matchObject.group()
'www'
>>> matchObject.span()
(0, 3)
- 在第 1 行,匯入模組 re
- 在第 2 行,在字串 ‘www.imooc.com’ 中查詢模式 ‘w+’
- 該模式匹配連續的小寫字元 W
- 如果找到模式匹配的子字串,則返回一個匹配物件 matchObject
- 在第 3 行,匹配物件 matchObject.group() 方法返回匹配的字串
- 在第 5 行,匹配物件 matchObject.span() 方法返回一個元組
- 元組的第 0 項,匹配的字串在原始字串中的起始位置
- 元組的第 1 項,匹配的字串在原始字串中的結束位置
>>> import re
>>> matchObject = re.match('W+', 'www.imooc.com')
>>> matchObject is None
True
- 在第 1 行,匯入模組 re
- 在第 2 行,在字串 ‘www.imooc.com’ 中查詢模式 ‘W+’
- 該模式匹配連續的大寫字元 W
- 如果找不到模式匹配的子字串,則返回一個 None
>>> import re
>>> matchObject = re.match('o+', 'www.imooc.com')
>>> matchObject is None
True
- 在第 1 行,匯入模組 re
- 在第 2 行,在字串 ‘www.imooc.com’ 中查詢模式 ‘o+’
- 該模式匹配連續的小寫字元 o
- 如果找不到模式匹配的子字串,則返回一個 None
- 在第 4 行,顯示匹配結果是 None
- 儘管字元 string 的中間含有字串 oo
- 函式 re.match 從字串 string 的開始位置進行匹配
- 因此找不到匹配
3.2 從字串的任意位置進行匹配
函式 re.search(pattern, string, flags = 0) 用於在字串查詢與模式匹配的字串:
- 從字串 string 的任意位置,查詢符合模式 pattern 的子串
- 如果匹配成功,則返回一個 re.MatchObject 物件
- 如果匹配失敗,則返回 None
- 引數 flags,用於控制正則表示式的匹配方式,如:是否區分大小寫,多行匹配等
>>> import re
>>> matchObject = re.search('o+', 'www.imooc.com')
>>> matchObject.group()
'oo'
>>> matchObject.span()
(6, 8)
- 在第 1 行,匯入模組 re
- 在第 2 行,在字串 ‘www.imooc.com’ 中查詢模式 ‘o+’
- 該模式匹配連續的小寫字元 o
- 如果找到模式匹配的子字串,則返回一個匹配物件 matchObject
- 在第 3 行,匹配物件 matchObject.group() 方法返回匹配的字串
- 在第 5 行,匹配物件 matchObject.span() 方法返回一個元組
- 元組的第 0 項,匹配的字串在原始字串中的起始位置
- 元組的第 1 項,匹配的字串在原始字串中的結束位置
3.3 在字串的首部進行匹配
>>> import re
>>> re.search('^a', 'abc')
<_sre.SRE_Match object; span=(0, 1), match='a'>
>>> re.search('^a', 'xabc')
>>>
- 在第 2 行,^a 表示從字串 ‘abc’ 的首部進行匹配
- 在第 3 行,顯示匹配結果不為 None
- 在第 4 行,^a 表示從字串 ‘xabc’ 的首部進行匹配
- 在第 5 行,顯示匹配結果為 None
3.4 在字串的尾部進行匹配
>>> import re
>>> re.search('c$', 'abc')
<_sre.SRE_Match object; span=(2, 3), match='c'>
>>> re.search('c$', 'abcx')
>>>
- 在第 2 行,c$ 表示從字串 ‘abc’ 的尾部進行匹配
- 在第 3 行,顯示匹配結果不為 None
- 在第 4 行,c$ 表示從字串 ‘xabc’ 的尾部進行匹配
- 在第 5 行,顯示匹配結果為 None
3.5 匹配一串數字
>>> import re
>>> re.search('\d+', 'abc123xyz')
<_sre.SRE_Match object; span=(3, 6), match='123'>
>>> re.search('\d{3}', 'abc123xyz')
<_sre.SRE_Match object; span=(3, 6), match='123'>
>>> re.search('\d{4}', 'abc123xyz')
>>>
- 在第 2 行,\d+ 表示匹配 1 個或者多個數字
- 在第 3 行,顯示匹配結果不為 None
- 在第 4 行,\d{3} 表示匹配 3 個數字
- 在第 5 行,顯示匹配結果不為 None
- 在第 6 行,\d+ 表示匹配 4 個數字
- 在第 7 行,顯示匹配結果為 None
3.6 判斷是否是合法的變數名
Python 的變數命名規則如下:
- 首個字元必須是字母或者字元 _
- 其餘的字元可以是字元、數字或者字元 _
下面的例子使用正則表示式判斷字串是否是一個合法的變數名稱:
import re
def isPythonId(id):
pattern = '^[a-zA-Z_][a-zA-Z0-9_]*$'
matchObject = re.search(pattern, id)
if matchObject is None:
print('%s is not Id' % id)
else:
print('%s is Id' % id)
isPythonId('abc')
isPythonId('Abc_123')
isPythonId('123')
- 在第 3 行,定義了函式 isPythonId(id),判斷輸入字串 id 是否是一個合法的 Python 變數名
- 在第 4 行,模式 pattern 定義了一個合法的 Python 變數名的模式,該模式由 4 個部分構成
模式 | 功能 |
---|---|
^ | 匹配字串頭部,即被匹配的字串從原始字串的頭部開始 |
[a-zA-Z_] | 匹配小寫字元、大寫字元和字元 _ |
[a-zA-Z0-9_] | 匹配小寫字元、大寫字元、數字和字元 _ |
* | 將 * 之前的字元重複 0 次或者多次 |
$ | 匹配字串尾部,即被匹配的字串以原始字串的尾部結尾 |
程式執行輸出結果如下:
abc is Id
Abc_123 is Id
123 is not Id
4. 將字串分割成多個部分
函式 re.split(pattern, string) 根據分隔符 pattern 將字串 string 分割
- 返回一個列表,該列表記錄了分割的字串
- 引數 pattern,描述了分隔符的模式
- 引數 string,是被分割的字串
>>> import re
>>> re.split('[ :]', 'www imooc:com')
['www', 'imooc', 'com']
>>> re.split(' +', 'www imooc com')
['www', 'imooc', 'com']
5. 在字串替換與模式匹配的字串
5.1 替換字串
函式 re.sub(pattern, replace, string, count=0, flags=0) 用於替換字串:
- 在字串 string 中查詢與模式 pattern 匹配的子串,將其替換為字串 replace
- 引數 replace,是被替換的字串,也可為一個函式
- 引數 count,模式匹配後替換的最大次數,預設 0 表示替換所有的匹配
- 引數 flags,用於控制正則表示式的匹配方式,如:是否區分大小寫,多行匹配等
import re
line = 'number = 123 # this is comment'
result = re.sub('\d+', 'NUMBER', line)
print(result)
result = re.sub('#.*$', '', line)
print(result)
- 在第 4 行,搜尋字串 line,將與模式 ‘\d+’ 匹配的字串替換為 ‘NUMBER’
- 模式 ‘\d+’ 匹配多個連續的數字
- 在第 6 行,搜尋字串 line,將與模式 ‘#.*$’ 匹配的字串替換為 ‘’
- 替換為空字串,即刪除匹配的字串
- 模式 ‘#.*$’ 匹配從字元 # 開始到到結尾的字串,即行的註釋
程式輸出結果:
number = NUMBER # this is comment
number = 123
- 在第 1 行,將數字 123 替換為 NUMBER
- 在第 1 行,將以 # 開始的註釋刪除
5.2 使用函式替換字串
引數 replace 用於替換匹配的字串,它可以是一個函式。下面的例子將匹配的數字乘以 2:
import re
def replace(matchedObject):
text = matchedObject.group()
number = int(text)
return str(number * 2)
line = 'number = 123'
result = re.sub('\d+', replace, line)
print(result)
- 在第 8 行,定義了原始字串 line
- 在第 9 行,使用 re.sub 搜尋符合模式 ‘\d+’ 的字串,使用函式 replace 進行替換
- re.sub 找到符合模式 ‘\d+’ 的字串時,將匹配結果傳遞給 replace
- 函式 replace 根據匹配結果,返回一個字串
- re.sub 將符合模式的字串替換為函式 replace 的返回結果
- 在第 3 行,定義了函式 replace
- 在第 4 行,matchedObject.group() 返回匹配模式的字串
- 在第 5 行,將匹配的字串轉換為整數
- 在第 6 行,將整數乘以 2 後轉換為字串,並返回
程式輸出結果如下:
number = 246
6. 分組與捕獲
6.1 簡介
正則表示式中的分組又稱為子表示式,就是把一個正則表示式的全部或部分當做一個整體進行處理,分成一個或多個組。其中分組是使用 () 表示的。進行分組之後 ()裡面的內容就會被當成一個整體來處理。
把正則表示式中子表示式匹配的內容,儲存到記憶體中以數字編號或顯式命名的組裡,方便後面引用,被稱為捕獲。
6.2 分析 URL
import re
def parseUrl(url):
pattern = '(.*)://(.*)/(.*)'
matchObject = re.search(pattern, url)
all = matchObject.group(0)
protocol = matchObject.group(1)
host = matchObject.group(2)
path = matchObject.group(3)
print('group(0) =', all)
print('group(1) =', protocol)
print('group(2) =', host)
print('group(3) =', path)
print()
parseUrl('https://www.imooc.com/wiki')
parseUrl('http://www.imooc.com/courses')
- 在第 3 行,函式 parseUrl(url) 分析 URL 的組成部分
- URL 由 3 部分構成:協議、主機名、路徑名
- 在第 4 行,定義了匹配 URL 的模式 ‘(.)://(.)/(.*)’
- 第 1 個 (.*) 匹配協議
- 第 2 個 (.*) 匹配主機名
- 第 3 個 (.*) 匹配路徑名
- 匹配物件 matchObject 的 group(index) 方法返回指定分組號的分組
- group(0) 為匹配整個表示式的字串
- group(1) 為匹配的協議
- group(2) 為匹配的主機名
- group(3) 為匹配的路徑名
程式執行輸出:
group(0) = https://www.imooc.com/wiki
group(1) = https
group(2) = www.imooc.com
group(3) = wiki
group(0) = http://www.imooc.com/courses
group(1) = http
group(2) = www.imooc.com
group(3) = courses