如何使用Python正則表達式模塊re(match、search、sub等)

商業

為了在 Python 中執行正則表達式處理,我們使用標準庫中的 re 模塊。它允許您使用正則表達式模式提取、替換和拆分字符串。

在本節中,我們將首先解釋 re 模塊的功能和方法。

  • 編譯正則表達式模式:compile()
  • 匹配對象
  • 檢查字符串的開頭是否匹配,提取:match()
  • 檢查不限於開頭的匹配項:search()
  • 檢查整個字符串是否匹配:fullmatch()
  • 獲取所有匹配零件的列表:findall()
  • 獲取所有匹配部分作為迭代器:finditer()
  • 更換匹配部分:sub(),subn()
  • 使用正則表達式模式拆分字符串:split()

之後,我將解釋可以在 re 模塊中使用的元字符(特殊字符)和正則表達式的特殊序列。基本上,它是標準的正則表達式語法,但要小心設置標誌(尤其是 re.ASCII)。

  • Python 中的正則表達式元字符、特殊序列和警告
  • 設置標誌
    • 僅限於 ASCII 字符:re.ASCII
    • 不區分大小寫:re.IGNORECASE
    • 匹配每一行的開頭和結尾:re.MULTILINE
    • 指定多個標誌
  • 貪婪和非貪婪匹配

編譯正則表達式模式: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 模式
特殊序列內容
\dUnicode 十進制數字(通過 ASCII 標誌限制為 ASCII 數字)
\D\d意思與此相反。
\sUnicode 空白字符(由 ASCII 標誌限制為 ASCII 空白字符)
\S\s意思與此相反。
\wUnicode 字字符和下劃線(僅限於 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

請注意,默認的貪婪匹配可能會匹配意外的字符串。