為了在 Python 中執行正則表達式處理,我們使用標準庫中的 re 模塊。它允許您使用正則表達式模式提取、替換和拆分字符串。
- re — Regular expression operations — Python 3.10.0 Documentation
- Regular Expression HOWTO — Python 3.10.0 Documentation
在本節中,我們將首先解釋 re 模塊的功能和方法。
- 編譯正則表達式模式:
compile()
- 匹配對象
- 檢查字符串的開頭是否匹配,提取:
match()
- 檢查不限於開頭的匹配項:
search()
- 檢查整個字符串是否匹配:
fullmatch()
- 獲取所有匹配零件的列表:
findall()
- 獲取所有匹配部分作為迭代器:
finditer()
- 更換匹配部分:
sub()
,subn()
- 使用正則表達式模式拆分字符串:
split()
之後,我將解釋可以在 re 模塊中使用的元字符(特殊字符)和正則表達式的特殊序列。基本上,它是標準的正則表達式語法,但要小心設置標誌(尤其是 re.ASCII)。
- Python 中的正則表達式元字符、特殊序列和警告
- 設置標誌
- 僅限於 ASCII 字符:
re.ASCII
- 不區分大小寫:
re.IGNORECASE
- 匹配每一行的開頭和結尾:
re.MULTILINE
- 指定多個標誌
- 僅限於 ASCII 字符:
- 貪婪和非貪婪匹配
編譯正則表達式模式:compile()
在re模塊中進行正則表達式處理有兩種方式。
用函數運行
第一個是函數。re.match()
,re.sub()
此類函數可用於使用正則表達式模式執行提取、替換和其他過程。
函數的詳細內容將在後面描述,但在所有函數中,第一個參數是正則表達式模式的字符串,其後是要處理的字符串等等。例如,在執行替換的re.sub()中,第二個參數是替換字符串,第三個參數是要處理的字符串。
import re
s = 'aaa@xxx.com, bbb@yyy.com, ccc@zzz.net'
m = re.match(r'([a-z]+)@([a-z]+)\.com', s)
print(m)
# <re.Match object; span=(0, 11), match='aaa@xxx.com'>
result = re.sub(r'([a-z]+)@([a-z]+)\.com', 'new-address', s)
print(result)
# new-address, new-address, ccc@zzz.net
請注意,本例中正則表達式模式中的 [a-z] 表示從 a 到 z 的任何字符(即小寫字母),而 + 表示重複前一個模式(在本例中為 [a-z])一次或多次。 [a-z]+ 匹配任何重複一個或多個小寫字母字符的字符串。
.是元字符(具有特殊含義的字符),必須用反斜杠轉義。
由於正則表達式模式字符串經常使用大量反斜杠,因此在示例中使用原始字符串很方便。
在正則表達式模式對象的方法中運行
在 re 模塊中處理正則表達式的第二種方法是正則表達式模式對象方法。
使用 re.compile(),您可以編譯正則表達式模式字符串以創建正則表達式模式對象。
p = re.compile(r'([a-z]+)@([a-z]+)\.com')
print(p)
# re.compile('([a-z]+)@([a-z]+)\\.com')
print(type(p))
# <class 're.Pattern'>
re.match()
,re.sub()
例如,與這些函數相同的過程可以作為正則表達式對象的方法 match(),sub() 執行。
m = p.match(s)
print(m)
# <re.Match object; span=(0, 11), match='aaa@xxx.com'>
result = p.sub('new-address', s)
print(result)
# new-address, new-address, ccc@zzz.net
下面描述的所有 re.xxx() 函數也作為正則表達式對象的方法提供。
如果您正在重複使用相同模式的過程,則使用 re.compile() 生成正則表達式對象並使用它會更有效。
在下面的示例代碼中,為了方便起見,該函數沒有編譯使用,但是如果要重複使用相同的模式,建議提前編譯並作為正則表達式對象的方法執行。
匹配對象
match()、search() 等返回一個匹配對象。
s = 'aaa@xxx.com'
m = re.match(r'[a-z]+@[a-z]+\.[a-z]+', s)
print(m)
# <re.Match object; span=(0, 11), match='aaa@xxx.com'>
print(type(m))
# <class 're.Match'>
使用匹配對象的以下方法獲取匹配的字符串和位置。
- 獲取比賽地點:
start()
,end()
,span()
- 獲取匹配的字符串:
group()
- 獲取每個組的字符串:
groups()
print(m.start())
# 0
print(m.end())
# 11
print(m.span())
# (0, 11)
print(m.group())
# aaa@xxx.com
如果使用括號() 將正則表達式模式的一部分括在字符串中,則該部分將作為一個組進行處理。在這種情況下,groups() 中與每個組匹配的部分的字符串可以作為元組獲取。
m = re.match(r'([a-z]+)@([a-z]+)\.([a-z]+)', s)
print(m)
# <re.Match object; span=(0, 11), match='aaa@xxx.com'>
print(m.groups())
# ('aaa', 'xxx', 'com')
檢查字符串的開頭是否匹配,提取:match()
如果字符串的開頭與模式匹配,match() 將返回一個匹配對象。
如上所述,匹配對象可用於提取匹配的子字符串,或者只是檢查是否匹配。
match() 只會檢查開頭。如果開頭沒有匹配的字符串,則返回 None。
s = 'aaa@xxx.com, bbb@yyy.com, ccc@zzz.net'
m = re.match(r'[a-z]+@[a-z]+\.com', s)
print(m)
# <re.Match object; span=(0, 11), match='aaa@xxx.com'>
m = re.match(r'[a-z]+@[a-z]+\.net', s)
print(m)
# None
檢查不限於開頭的匹配項,提取:search()
與 match() 一樣,如果匹配則返回一個匹配對象。
如果有多個匹配部分,則只返回第一個匹配部分。
s = 'aaa@xxx.com, bbb@yyy.com, ccc@zzz.net'
m = re.search(r'[a-z]+@[a-z]+\.net', s)
print(m)
# <re.Match object; span=(26, 37), match='ccc@zzz.net'>
m = re.search(r'[a-z]+@[a-z]+\.com', s)
print(m)
# <re.Match object; span=(0, 11), match='aaa@xxx.com'>
如果您想獲取所有匹配的部分,請使用 findall() 或 finditer() ,如下所述。
檢查整個字符串是否匹配: fullmatch()
要檢查整個字符串是否與正則表達式模式匹配,請使用 fullmatch()。例如,這對於檢查字符串作為電子郵件地址是否有效很有用。
如果整個字符串匹配,則返回匹配對象。
s = 'aaa@xxx.com'
m = re.fullmatch(r'[a-z]+@[a-z]+\.com', s)
print(m)
# <re.Match object; span=(0, 11), match='aaa@xxx.com'>
如果有不匹配的部分(只有部分匹配或根本沒有匹配),則返回 None 。
s = '!!!aaa@xxx.com!!!'
m = re.fullmatch(r'[a-z]+@[a-z]+\.com', s)
print(m)
# None
fullmatch() 是在 Python 3.4 中添加的。如果您想在早期版本中執行相同的操作,請在末尾使用 match() 和匹配的元字符 $。如果從頭到尾的整個字符串不匹配,則返回 None。
s = '!!!aaa@xxx.com!!!'
m = re.match(r'[a-z]+@[a-z]+\.com$', s)
print(m)
# None
獲取所有匹配部分的列表:findall()
findall() 返回所有匹配子字符串的列表。請注意,列表的元素不是匹配對象而是字符串。
s = 'aaa@xxx.com, bbb@yyy.com, ccc@zzz.net'
result = re.findall(r'[a-z]+@[a-z]+\.[a-z]+', s)
print(result)
# ['aaa@xxx.com', 'bbb@yyy.com', 'ccc@zzz.net']
可以使用內置函數 len() 檢查匹配部分的數量,該函數返回列表中的元素數量。
print(len(result))
# 3
在正則表達式模式中用括號() 分組返回一個元組列表,其元素是每個組的字符串。這相當於匹配對像中的 groups()。
result = re.findall(r'([a-z]+)@([a-z]+)\.([a-z]+)', s)
print(result)
# [('aaa', 'xxx', 'com'), ('bbb', 'yyy', 'com'), ('ccc', 'zzz', 'net')]
組括號 () 可以嵌套,因此如果您還想獲得整個匹配項,只需將整個匹配項括在括號 () 中。
result = re.findall(r'(([a-z]+)@([a-z]+)\.([a-z]+))', s)
print(result)
# [('aaa@xxx.com', 'aaa', 'xxx', 'com'), ('bbb@yyy.com', 'bbb', 'yyy', 'com'), ('ccc@zzz.net', 'ccc', 'zzz', 'net')]
如果未找到匹配項,則返回一個空元組。
result = re.findall('[0-9]+', s)
print(result)
# []
獲取所有匹配的部分作為迭代器:finditer()
finditer() 將所有匹配的部分作為迭代器返回。元素不是像 findall() 那樣的字符串,而是匹配對象,所以你可以得到匹配部分的位置(索引)。
迭代器本身不能用 print() 打印出來以獲取其內容。如果使用內置函數next()或者for語句,可以一一獲取內容。
s = 'aaa@xxx.com, bbb@yyy.com, ccc@zzz.net'
result = re.finditer(r'[a-z]+@[a-z]+\.[a-z]+', s)
print(result)
# <callable_iterator object at 0x10b0efa90>
print(type(result))
# <class 'callable_iterator'>
for m in result:
print(m)
# <re.Match object; span=(0, 11), match='aaa@xxx.com'>
# <re.Match object; span=(13, 24), match='bbb@yyy.com'>
# <re.Match object; span=(26, 37), match='ccc@zzz.net'>
也可以使用 list() 將其轉換為列表。
l = list(re.finditer(r'[a-z]+@[a-z]+\.[a-z]+', s))
print(l)
# [<re.Match object; span=(0, 11), match='aaa@xxx.com'>, <re.Match object; span=(13, 24), match='bbb@yyy.com'>, <re.Match object; span=(26, 37), match='ccc@zzz.net'>]
print(l[0])
# <re.Match object; span=(0, 11), match='aaa@xxx.com'>
print(type(l[0]))
# <class 're.Match'>
print(l[0].span())
# (0, 11)
如果你想得到所有匹配部分的位置,list comprehension 表示法比 list() 更方便。
print([m.span() for m in re.finditer(r'[a-z]+@[a-z]+\.[a-z]+', s)])
# [(0, 11), (13, 24), (26, 37)]
迭代器按順序取出元素。請注意,如果您在到達終點後嘗試提取更多元素,您將一無所有。
result = re.finditer(r'[a-z]+@[a-z]+\.[a-z]+', s)
for m in result:
print(m)
# <re.Match object; span=(0, 11), match='aaa@xxx.com'>
# <re.Match object; span=(13, 24), match='bbb@yyy.com'>
# <re.Match object; span=(26, 37), match='ccc@zzz.net'>
print(list(result))
# []
替換匹配部分:sub()、subn()
使用 sub(),您可以用另一個字符串替換匹配的部分。將返回替換的字符串。
s = 'aaa@xxx.com, bbb@yyy.com, ccc@zzz.net'
result = re.sub(r'[a-z]+@[a-z]+\.com', 'new-address', s)
print(result)
# new-address, new-address, ccc@zzz.net
print(type(result))
# <class 'str'>
當用括號()分組時,匹配的字符串可以用在替換的字符串中。
默認情況下,支持以下內容:請注意,對於不是原始字符串的普通字符串,必須在反斜杠之前列出反斜杠以轉義反斜杠。
\1 | 第一個括號 |
\2 | 第二個括號 |
\3 | 第三個括號 |
result = re.sub(r'([a-z]+)@([a-z]+)\.com', r'\1@\2.net', s)
print(result)
# aaa@xxx.net, bbb@yyy.net, ccc@zzz.net
?P<xxx>
如果通過在正則表達式模式的括號的開頭寫下這個來命名組,則可以使用名稱而不是數字來指定它,如下所示。\g<xxx>
result = re.sub(r'(?P<local>[a-z]+)@(?P<SLD>[a-z]+)\.com', r'\g<local>@\g<SLD>.net', s)
print(result)
# aaa@xxx.net, bbb@yyy.net, ccc@zzz.net
參數計數指定最大替換次數。只會替換左側的計數。
result = re.sub(r'[a-z]+@[a-z]+\.com', 'new-address', s, count=1)
print(result)
# new-address, bbb@yyy.com, ccc@zzz.net
subn() 返回替換字符串的元組(與 sub() 的返回值相同)和替換部分的數量(與模式匹配的數字)。
result = re.subn(r'[a-z]+@[a-z]+\.com', 'new-address', s)
print(result)
# ('new-address, new-address, ccc@zzz.net', 2)
指定參數的方法與 sub() 相同。您可以使用按括號分組的部分,或指定參數計數。
result = re.subn(r'(?P<local>[a-z]+)@(?P<SLD>[a-z]+)\.com', r'\g<local>@\g<SLD>.net', s)
print(result)
# ('aaa@xxx.net, bbb@yyy.net, ccc@zzz.net', 2)
result = re.subn(r'[a-z]+@[a-z]+\.com', 'new-address', s, count=1)
print(result)
# ('new-address, bbb@yyy.com, ccc@zzz.net', 1)
使用正則表達式模式拆分字符串:split()
split() 在匹配模式的部分拆分字符串,並將其作為列表返回。
請注意,第一個和最後一個匹配項將在結果列表的開頭和結尾包含空字符串。
s = '111aaa222bbb333'
result = re.split('[a-z]+', s)
print(result)
# ['111', '222', '333']
result = re.split('[0-9]+', s)
print(result)
# ['', 'aaa', 'bbb', '']
maxsplit 參數指定最大分割數(片)。只會拆分左側的計數。
result = re.split('[a-z]+', s, 1)
print(result)
# ['111', '222bbb333']
Python 中的正則表達式元字符、特殊序列和警告
Python 3 re模塊中可以使用的主要正則表達式元字符(特殊字符)和特殊序列如下
元字符 | 內容 |
---|---|
. | 除換行符以外的任何單個字符(包括帶有 DOTALL 標誌的換行符) |
^ | 字符串的開頭(也匹配每一行的開頭和 MULTILINE 標誌) |
$ | 字符串的結尾(也用 MULTILINE 標誌匹配每一行的結尾) |
* | 重複上一個模式 0 次以上 |
+ | 重複之前的模式至少一次。 |
? | 重複之前的模式 0 或 1 次 |
{m} | 重複之前的模式 m 次 |
{m, n} | 最後一個圖案。m ~n 重複 |
[] | 一組字符[] 匹配這些字符中的任何一個 |
| | 或者A|B 匹配 A 或 B 模式 |
特殊序列 | 內容 |
---|---|
\d | Unicode 十進制數字(通過 ASCII 標誌限制為 ASCII 數字) |
\D | \d 意思與此相反。 |
\s | Unicode 空白字符(由 ASCII 標誌限制為 ASCII 空白字符) |
\S | \s 意思與此相反。 |
\w | Unicode 字字符和下劃線(僅限於 ASCII 字母數字字符和下劃線由 ASCII 標誌) |
\W | \w 意思與此相反。 |
並非所有這些都列在此表中。有關完整列表,請參閱官方文檔。
另請注意,某些含義在 Python 2 中有所不同。
設置標誌
如上表所示,一些元字符和特殊序列會根據標誌改變它們的模式。
此處僅涵蓋主要標誌。其餘的請參閱官方文檔。
限於 ASCII 字符:re.ASCII
\w
默認情況下,這也將匹配雙字節漢字、字母數字字符等,用於 Python 3 字符串。它不等同於以下內容,因為它不是標準的正則表達式。[a-zA-Z0-9_]
m = re.match(r'\w+', '漢字ABC123')
print(m)
# <re.Match object; span=(0, 11), match='漢字ABC123'>
m = re.match('[a-zA-Z0-9_]+', '漢字ABC123')
print(m)
# None
如果在每個函數中為參數標誌指定 re.ASCII,或者在正則表達式模式字符串的開頭添加以下內聯標誌,它將只匹配 ASCII 字符(不會匹配雙字節日文、字母數字字符等) .)(?a)
在這種情況下,以下兩個是等效的。\w
#ERROR![a-zA-Z0-9_]
m = re.match(r'\w+', '漢字ABC123', flags=re.ASCII)
print(m)
# None
m = re.match(r'(?a)\w+', '漢字ABC123')
print(m)
# None
使用 re.compile() 編譯時同樣適用。使用參數標誌或內聯標誌。
p = re.compile(r'\w+', flags=re.ASCII)
print(p)
# re.compile('\\w+', re.ASCII)
print(p.match('漢字ABC123'))
# None
p = re.compile(r'(?a)\w+')
print(p)
# re.compile('(?a)\\w+', re.ASCII)
print(p.match('漢字ABC123'))
# None
ASCII 也可作為縮寫形式使用。 A. 兩者都可以使用。
print(re.ASCII is re.A)
# True
\W,與\W 相反,也受re.ASCII 和內聯標誌的影響。
m = re.match(r'\W+', '漢字ABC123')
print(m)
# None
m = re.match(r'\W+', '漢字ABC123', flags=re.ASCII)
print(m)
# <re.Match object; span=(0, 11), match='漢字ABC123'>
與 \w 一樣,默認情況下,以下兩個匹配單字節和雙字節字符,但如果指定了 re.ASCII 或內聯標誌,則僅限於單字節字符。
- 匹配數字
\d
- 匹配一個空格
\s
- 匹配非數字
\D
- 匹配任何非空格。
\S
m = re.match(r'\d+', '123')
print(m)
# <re.Match object; span=(0, 3), match='123'>
m = re.match(r'\d+', '123')
print(m)
# <re.Match object; span=(0, 3), match='123'>
m = re.match(r'\d+', '123', flags=re.ASCII)
print(m)
# <re.Match object; span=(0, 3), match='123'>
m = re.match(r'\d+', '123', flags=re.ASCII)
print(m)
# None
m = re.match(r'\s+', ' ') # full-width space
print(m)
# <re.Match object; span=(0, 1), match='\u3000'>
m = re.match(r'\s+', ' ', flags=re.ASCII)
print(m)
# None
不區分大小寫:re.IGNORECASE
默認情況下,它區分大小寫。要匹配兩者,您需要在模式中同時包含大寫和小寫字母。
re.IGNORECASE
如果指定了此項,它將不區分大小寫地匹配。相當於標準正則表達式中的 i 標誌。
m = re.match('[a-zA-Z]+', 'abcABC')
print(m)
# <re.Match object; span=(0, 6), match='abcABC'>
m = re.match('[a-z]+', 'abcABC', flags=re.IGNORECASE)
print(m)
# <re.Match object; span=(0, 6), match='abcABC'>
m = re.match('[A-Z]+', 'abcABC', flags=re.IGNORECASE)
print(m)
# <re.Match object; span=(0, 6), match='abcABC'>
您可以使用小於或等於。
- 內聯標誌
(?i)
- 縮寫
re.I
匹配每一行的開頭和結尾:re.MULTILINE
^
此正則表達式中的元字符與字符串的開頭匹配。
默認情況下,只匹配整個字符串的開頭,但以下內容也將匹配每一行的開頭。相當於標準正則表達式中的 m 標誌。re.MULTILINE
s = '''aaa-xxx
bbb-yyy
ccc-zzz'''
print(s)
# aaa-xxx
# bbb-yyy
# ccc-zzz
result = re.findall('[a-z]+', s)
print(result)
# ['aaa', 'xxx', 'bbb', 'yyy', 'ccc', 'zzz']
result = re.findall('^[a-z]+', s)
print(result)
# ['aaa']
result = re.findall('^[a-z]+', s, flags=re.MULTILINE)
print(result)
# ['aaa', 'bbb', 'ccc']
$
匹配字符串的結尾。默認情況下,只匹配整個字符串的結尾。re.MULTILINE
如果您指定此項,它也將匹配每行的結尾。
result = re.findall('[a-z]+$', s)
print(result)
# ['zzz']
result = re.findall('[a-z]+$', s, flags=re.MULTILINE)
print(result)
# ['xxx', 'yyy', 'zzz']
您可以使用小於或等於。
- 內聯標誌
(?m)
- 縮寫
re.M
指定多個標誌
|
如果要同時啟用多個標誌,請使用此選項。對於內聯標誌,每個字符後面必須跟一個字母,如下所示。(?am)
s = '''aaa-xxx
漢漢漢-字字字
bbb-zzz'''
print(s)
# aaa-xxx
# 漢漢漢-字字字
# bbb-zzz
result = re.findall(r'^\w+', s, flags=re.M)
print(result)
# ['aaa', '漢漢漢', 'bbb']
result = re.findall(r'^\w+', s, flags=re.M | re.A)
print(result)
# ['aaa', 'bbb']
result = re.findall(r'(?am)^\w+', s)
print(result)
# ['aaa', 'bbb']
貪婪和非貪婪匹配
這是正則表達式的一個普遍問題,不僅僅是 Python 的問題,但我會寫下它,因為它往往會讓我陷入困境。
默認情況下,以下是貪婪匹配,它匹配最長的字符串。
*
+
?
s = 'aaa@xxx.com, bbb@yyy.com'
m = re.match(r'.+com', s)
print(m)
# <re.Match object; span=(0, 24), match='aaa@xxx.com, bbb@yyy.com'>
print(m.group())
# aaa@xxx.com, bbb@yyy.com
這 ?之後它將導致非貪婪的最小匹配,匹配最短的字符串。
*?
+?
??
m = re.match(r'.+?com', s)
print(m)
# <re.Match object; span=(0, 11), match='aaa@xxx.com'>
print(m.group())
# aaa@xxx.com
請注意,默認的貪婪匹配可能會匹配意外的字符串。