使用者:Xyy23330121/Python/正則表達式
正則表達式是處理、分割字符串的重要工具。它可以以一定的模式對字符串進行「匹配」,從而可以按模式篩選字符串或子字符串,並對篩選結果進行操作。
以下語法中,一部分為 Python 的正則表達式專屬。在其它的正則表達式實現中使用可能會出錯。
正則表達式中,大多數字符都只匹配其自身。比如:正則表達式中 t
會唯一的匹配字符 t
,而正則表達式 e
會唯一的匹配字符 e
等等。
正則表達式滿足相加性質。即當「正則表達式A」匹配「字符串a」,而「正則表達式B」匹配「字符串b」時。拼接 A 和 B 得到的「正則表達式A+B」,一定匹配拼接得到的「字符串a+b」。
比如,正則表達式 test
會唯一的匹配字符串 test
。
對於正則表達式而言,上面的「基本匹配」是不足以滿足「按模式匹配」的需要的。為了描述模式,正則表達式設置了以下的元字符:
. ^ $ * + ? { } [ ] \ | ( )
這些元字符在正則表達式中表示特殊的意思,從而也就不能匹配字符串中相同字符。元字符之外的字符,都只匹配其自身。
以下將講解元字符的用法。
匹配位於字符串開頭的空字符串。比如:
- 正則表達式
a
和字符串aba
匹配,會得到兩個子字符串a
。 - 而正則表達式
^a
和字符串aba
匹配,只會匹配到位於字符串開頭的子字符串。
匹配字符串末尾的空字符串,如果字符串末尾是換行符,也可以匹配該換行符前的空字符串。
通過 \
,我們可以在正則表達式轉義出匹配元字符的表達式。比如:
\[
唯一地匹配字符[
。\\
唯一地匹配字符\
。
創建正則表達式時,是通過字符串來創建的。如果要創建正則表達式 \\
,需要使用字符串 "\\\\"
或 r"\\"
。為了簡便和易於閱讀,在創建正則表達式時,請務必使用「原始字符串」的方式。
Python 字符串支持的、以下轉義序列也被用作正則表達式的轉義。
轉義字符 | 含義 |
---|---|
\a |
響鈴(BEL) |
\b |
退格(BS) |
\f |
換頁(FF) |
\n |
換行(LF) |
\r |
回車(CR) |
\t |
橫向制表符(TAB) |
\v |
縱向制表符(VT) |
\ooo |
八進制數ooo 所表示的字符
|
\xhh |
二位十六進制數hh 所表示的字符
|
\N{name} |
Unicode 數據庫中,名為name 的字符,name 可以為別名。有些別名不被支持。
|
\uxxxx |
四位十六進制數xxxx 所表示的字符
|
\Uxxxxxxxx |
八位十六進制數xxxxxxxx 表示的字符
|
特別的,以下幾種方式與字符串中的使用有區別:
- 對於
\ooo
,只有以下兩種方式會被視為此轉義:- 首位數字是
0
,比如\01
- 使用了完整的三位數字,比如
\123
- 首位數字是
- 對於
\b
,只有在「字符類」中,才認為是退格符。 - 對於
\uxxxx
、\Uxxxxxxxx
和\N{name}
,僅在 Unicode 字符串模式才會被識別。
轉義 | 解釋 |
---|---|
\A | 只匹配字符串開頭 |
\b | 只匹配單詞開頭或結尾 單詞開頭和結尾被定義為:
|
\B | 非 \b 的空字符串 |
\Z | 只匹配字符串結尾 |
關於 \w
和 \W
,參見下面 ASCII 模式和 Unicode 模式 章節。
被 []
括住的部分為字符類。字符類會匹配任何在類中的字符,比如:[aA1]
能匹配三個字符串:a
、A
和 1
。
除\
以外的元字符在字符類中都不起作用。比如:[$]
會唯一的匹配字符 $
。
如果要添加 ]
到字符類中,則必須要使用轉義方法 \]
。
除上述規則以外,為簡便起見,字符類有以下的特殊的表示方式:
用 -
可以在字符類中表示「範圍」,比如:
[a-c]
能匹配a
、b
、c
。[a-zA-Z0-9]
能匹配任何單個英文字母字符。
需要注意的是,如果把 -
放在首尾,則不會表示「範圍」,而是表示單獨的 -
字符。比如:
[-a]
能匹配-
、a
。[-a-c]
能匹配-
、a
、b
、c
。
若字符類中第一個字符為 ^
,則認為是對字符類取反,比如:
[^a]
能匹配任何不是a
的字符。[^a-z]
能匹配任何不是小寫英文字母的字符。
需要注意的是,如果把 ^
放在中間,則沒有此效果,比如:
[ ^]
能匹配空格字符和^
用 .
可以匹配除了換行符以外的任意字符。
通過以下的方式,可以設定匹配次數。
正則表達式 ab*
會匹配字符串 a
、ab
、abb
等。
正則表達式 ab+
會匹配字符串 ab
、abb
等。但不會匹配 a
。
正則表達式 ab?
會匹配字符串 a
和字符串 ab
。
比如:正則表達式 a{3}
會匹配字符串 aaa
。
使用 a{m}
時,m 必須是正整數。
m 和 n 必須是正整數。
比如:正則表達式 a{2,3}
會匹配字符串 aa
和字符串 aaa
。
特別的,使用 {,n}
會匹配 0 ~ n 次。使用 {m,}
會匹配 m ~ 任意次。使用 {,}
等同於使用 *
。
在進行重複匹配時,默認會先儘可能多地進行匹配。在正則表達式 ^[a-z]*re
和 abrer
進行匹配時,會進行以下步驟:
[a-z]*
匹配abrer
r
到字符串末尾,無法匹配,回退
[a-z]*
匹配abre
r
匹配r
e
到字符串末尾,無法匹配,回退
[a-z]*
匹配abr
r
無法匹配e
,回退
[a-z]*
匹配ab
r
匹配r
e
匹配e
- 正則表達式結束,匹配完畢,匹配到子字符串
abre
。
可以看出 [a-z]*
會先嘗試匹配 5 個字符、再嘗試匹配 4 個字符,從匹配字符數從多到少進行嘗試。這種嘗試的模式被稱為「貪婪模式」。
在代表重複匹配的結構後添加 ?
,則會改為匹配字符數從少到多進行嘗試。
比如,在正則表達式 ^[a-z]*?re
和 abrer
進行匹配時,會進行以下步驟:
[a-z]*?
匹配a
r
無法匹配b
,回退
[a-z]*?
匹配ab
r
匹配r
e
匹配e
- 正則表達式結束,匹配完畢,匹配到子字符串
abre
。
在代表重複匹配的結構後添加 +
,則會在貪婪模式的基礎上,不允許回退。這也被稱為「占有模式」。
比如,在正則表達式 ^a?[a-z]*+re
和 abrer
進行匹配時,會進行以下步驟:
a?
匹配a
[a-z]*+
匹配brer
並「占有」r
無法匹配字符串末尾,回退
不能回退並修改[a-z]*+
的匹配結果
回退並修改在[a-z]*+
之前的a?
a?
匹配空字符串[a-z]*+
匹配abrer
並「占有」r
無法匹配字符串末尾,回退
不能回退並修改[a-z]*+
的匹配結果
- 正則表達式結束,匹配完畢,沒有匹配到字符串。
「占有模式」和「非貪婪模式」不能同時設置。
正則表達式 a|b
會匹配字符串 a
和 b
。
特別的,如果左邊的「分支」匹配成功了,就不會嘗試對右邊的分支進行匹配。比如:
[a-z]*|[0-9]*114
和字符串進行匹配時,會先匹配 [a-z]*114
,匹配失敗後再嘗試匹配 [0-9]*114
。如果 [a-z]*114
匹配成功,就不會嘗試 [0-9]*114
了。舉個例子:
[a-z]*|[0-9]*114
和1140114
匹配:- 嘗試匹配
[a-z]*114
[a-z]*
匹配到空字符串114
匹配到114
匹配成功,匹配到114
- 嘗試匹配
在匹配過程中,根本不會嘗試匹配 [0-9]*114
。即便 [0-9]*114
能匹配完整的 1140114
。
我們可以把一系列正則表達式作成組合,並對組合進行操作。比如:
- 正則表達式
(ab)?
會匹配字符串ab
和字符串abab
- 正則表達式
(ab)|c
會匹配字符串ab
和字符串c
字符 | 匹配方式 |
---|---|
i |
忽略大小寫 |
m |
允許多行匹配 |
s |
. 會匹配任意字符(包括換行符) |
x |
帶注釋的正則表達式 |
a |
ASCII 模式 |
u |
Unicode 模式 |
L |
按本地語言區域模式 |
以上的匹配方式並不絕對。如果在正則表達式中,出現了形如 (?a)
的組,則會按照該組的內容設置匹配方式。
這種類型的組不會匹配任何內容,它只匹配空字符串。
右表列出了該組中,可用的字符。這些字符可以進行組合,以同時設置多個匹配方式。
其中,ASCII 模式、 Unicode 模式和「按本地語言區域」模式互相矛盾,因此不能相互組合。在未設置這些模式時,默認使用 Unicode 模式。「按本地語言區域」的模式不可靠,不建議使用。
ASCII 模式和 Unicode 模式設置以下轉義得到的字符類的行為:
轉義 | ASCII 模式 | Unicode 模式 |
---|---|---|
\d | [0-9] | 任何屬於 Unicode 字符類別 ND 的字符。 |
\D | [^0-9] | 任何非 \d 的字符。
|
\s | [ \t\n\r\f\v] | 任何 Unicode 空白字符 |
\S | [^ \t\n\r\f\v] | 任何非 Unicode 空白字符 |
\w | [a-zA-Z0-9_] | 任何 str.isalnum() 方法返回 True 的字符 以及下劃線 |
\W | [^a-zA-Z0-9_] | 任何 str.isalnum() 方法返回 False 的字符 不包含下環線 |
多行匹配模式會影響 ^
和 $
的行為。
在設置了多行匹配模式後:
^
會匹配位於換行符後的空字符串。$
會匹配位於換行符前的空字符串。
此時,允許創建帶有注釋的、美觀的正則表達式。以下兩種方式得到的結果是相同的:
pattern = r"(0b[01]*)|(0o[0-7]*)|([0-9]*)|(0x[0-9a-fA-F]*)"
r = re.compile(pattern) #编译正则表达式
pattern = r"""(?x) #匹配数字:
(0b[01]*) #二进制数字
| (0o[0-7]*) #八进制数字
| ([0-9]*) #十进制数字
| (0x[0-9a-fA-F]*) #十六进制数字
"""
r = re.compile(pattern) #编译正则表达式
此時,正則表達式會忽略字符串中的空格,以及多行字符串中、自動加入的換行符。
如果要匹配空格,則使用 \
。如果要匹配換行符,則使用 \n
。
僅對其中的正則表達式啟用或棄用某匹配方式。比如:
(?i)ab
會匹配:ab
、Ab
、aB
和AB
。(?i)a(?-i:b)
會匹配:ab
、Ab
。棄用了對b
的「忽略大小寫模式」。a(?i:b)
會匹配:aB
、AB
。僅對b
啟用「忽略大小寫模式」。
特別的,不允許簡單地棄用「ASCII 模式」「Unicode 模式」和「按本地語言區域模式」。
該組會對組中的表達式啟用「占有模式」,比如:
x*+
等同於(?>x*)
。
此方法會影響 Python 在進行匹配時、保存的結果。比如:
(xyz)
在匹配時,會在結果中保存匹配到的xyz
。(?:xy(z))
在匹配時,會匹配到相同的內容,但結果中僅保存z
。
通過這種方式,可以給組進行命名。re.Match 實例的多個方法,以及 re.Pattern 實例的 sub 方法會引用命名組匹配到的內容。
被命名的組可以用以下方式,在正則表達式中引用。
僅匹配第一次匹配到 (?P<name>...)
時、匹配到的內容。
import re
p = re.compile(r'''(?x)
<(?P<ele_name>[a-zA-Z]+).*?> #匹配<html>
.* #匹配中间
</(?P=ele_name)> #匹配</html>
''')
s = '<p>Paragraph</pi>'
s1 = '<p>Paragraph</p>'
print(p.match(s).string[matchobj.start():matchobj.end()]) #输出空字符串,没有得到匹配。
print(p.match(s1).string[matchobj.start():matchobj.end()]) #输出:<p>Paragraph</p>
類似 (?P=name)
,但此方式也可以用於引用一般的組。
僅匹配第一次匹配到第 num 個組時,匹配到的內容。
作為注釋使用,僅匹配空字符串。
僅匹配空字符串。
其中的正則表達式和當前位置右側的內容相匹配,則匹配空字符串成功。否則匹配失敗。
比如:正則表達式 Isaac(?= Newton)
將會匹配 Isaac Newton
中的 Isaac
,但不會匹配 Isaac Brown
中的 Isaac
。
這種方式被稱為前視斷言 (lookahead assertion)。
僅匹配空字符串。
其中的正則表達式和當前位置右側的內容不匹配,則匹配空字符串成功。否則匹配失敗。
比如:正則表達式 Isaac(?! Newton)
將會匹配 Isaac Brown
中的 Isaac
,但不會匹配 Isaac Newton
中的 Isaac
。
這種方式被稱為否定型前視斷言 (negative lookahead assertion)。
僅匹配空字符串。
其中的正則表達式與當前位置左側的內容相匹配,則匹配空字符串成功。否則匹配失敗。
這種方式被稱為肯定型後視斷言 (positive lookbehind assertion)。
僅匹配空字符串。
其中的正則表達式和當前位置左側的內容不匹配,則匹配空字符串成功。否則匹配失敗。
這種方式被稱為否定型後視斷言 (negative lookbehind assertion)。
在匹配時,之前已有的、\num
或 (?P=name)
所示的組、匹配的並非空字符串,則按照 yes-pattern 進行匹配。否則,按照 no-pattern 進行匹配。
旗標 | 匹配方式 |
---|---|
re.I re.IGNORECASE |
忽略大小寫 |
re.M re.MULTILINE |
允許多行匹配 |
re.S re.DOTALL |
. 會匹配任意字符(包括換行符) |
re.X re.VERBOSE |
帶注釋的正則表達式 |
re.A re.ASCII |
ASCII 模式 |
re.U re.UNICODE |
Unicode 模式 |
re.L re.LOCALE |
按本地語言區域模式 |
re.NOFLAG |
表示空旗標。 特別的:re.NOFLAG 就是整數 0 。
|
re.DEBUG |
顯示有關編譯表達式的調試信息。 |
旗標和上述 設置匹配方式 章節的效果是相同的。右表列出了可用的旗標。
在使用旗標時,可以用「按位或」運算符 |
組合多個旗標,並把結果傳入下面函數中的 flags 參數中。
特別的:re.NOFLAG
和任何其它旗標進行組合,都會返回其它旗標。
re.DEBUG
會在使用正則表達式時,返回編譯的結果。
編譯正則表達式。返回一個 re.Pattern
實例。re 模塊是通過該類型的實例方法來進行匹配操作的。
屬性 | 解釋 |
---|---|
flags | 旗標。是 compile 函數獲取的旗標以及正則表達式中用 (?imsxauL) 設置的匹配方式的組合。
|
groups | 捕獲到的模式串中組的數量。 |
groupindex | 映射由 (?P<name>) 定義的、有命名的組,其命名:索引位置 組成的的字典。如果沒有有命名的組,那字典就是空的。 |
pattern | 編譯對象的原始樣式字符串。 |
在 string[pos: endpos]
中,查找匹配正則表達式的第一個位置,並返回對應的 re.Match
實例。如果沒有匹配的位置,則返回 None。
類似 search
方法,但是從 string[pos: endpos]
的開頭開始匹配:不在 string[pos: endpos]
最開頭的、匹配的位置都不會被匹配到。
如果 string[pos: endpos]
整個字符串都與正則表達式匹配,則返回相應的 re.Match
實例。
在 string[pos: endpos]
中,查找匹配正則表達式的、所有非重複的位置。
每個位置,都會生成一個 re.Match
實例。該函數會返回生成的 re.Match
實例組成的迭代器。
類似 finditer
方法。但是輸出不同。
如果正則表達式中不包含一般的組,則會輸出匹配的子字符串組成的列表。而如果正則表達式中包含組,則只輸出組中的內容。比如:
import re
p = re.compile(r"\b[tT]\w+\b")
s1 = "This is a test string."
print(p.findall(s1)) #输出:['This', 'test']
q = re.compile(r"\b(\w+)=([^ ]+)")
s2 = '<img src="./image.png" width=100 height=100 />'
print(q.findall(s2)) #输出:[('src', '"./image.png"'), ('width', '100'), ('height', '100')]
以 string
中所有匹配的子字符串為分割符,返回分割後的、子字符串的列表。
最多進行 maxsplit
次分割。
特別的:
- 如果 string 的開頭被匹配,則返回的列表會以空字符串開頭。
- 如果正則表達式中包含一般的組(而非
(?:...)
的組),則會在分割中保留組內匹配的內容。
import re
p = re.compile("(a)(b)")
q = re.compile("(?:a)(?:b)")
s = "ab"
print(p.split(s)) #输出:['c', 'a', 'b', 'c']
print(q.split(s)) #输出:['c', 'c']
可以看出,使用一般的組的 p
在進行分割時,輸出的結果中包含了組中的內容。而使用非捕獲模式的 q
則沒有進行這樣的操作。
按 repl 所示的模式替換 string 中、匹配的內容。最多替換 count 次。repl 可以是字符串或函數。
如果 repl 是字符串,則直接進行替換,比如:
import re
p = re.compile(r"\b(\w+)=([^ ]+)")
s = '<img src="./image.png" width=100 height=100 />'
r = 'REPLACE'
print(p.sub(r, s)) #输出:<img REPLACE REPLACE REPLACE />
特別的, repl 中包含的、字符串轉義序列的原始序列都會被轉義。並且一些特殊的轉義也會被執行:
轉義 | 注釋 |
---|---|
\g<num> |
正則表達式中,第 num 個組匹配到的內容 |
\num |
正則表達式中,第 num 個組匹配到的內容 由於和 \ooo 衝突,num 不能超過 100,且開頭不能為 0。 |
\g<name> |
正則表達式中,名為 name 的組匹配到的內容 |
import re
p = re.compile(r"\b(?P<arg>\w+)=([^ ]+)")
s = '<img src="./image.png" />'
r = r'\g<1>\1\g<arg>'
print(p.sub(r, s)) #输出:<img srcsrcsrc />
如果 repl 是函數,則該函數應可以傳入每次匹配時,對應的 re.Match
實例,並返回替換的結果。
import re
p = re.compile(r"\b(\w+)=([^ ]+)")
s = '<img src="./image.png" width=100 height=100 />'
def r(matchobj):
print(matchobj.string[matchobj.start():matchobj.end()])
return "REPLACE"
print(p.sub(r, s))
輸出為:
src="./image.png"
width=100
height=100
<img REPLACE REPLACE REPLACE />
類似 sub
方法,但返回一個元組: (替换后字符串, 替换次数)
。
如果只匹配一次,也可以直接使用 re 模塊的以下函數:
函數 | 等同於 |
---|---|
re.search(pattern, string, flags=0) | re.compile(pattern, flags).search(string) |
re.match(pattern, string, flags=0) | re.compile(pattern, flags).match(string) |
re.fullmatch(pattern, string, flags=0) | re.compile(pattern, flags).fullmatch(string) |
re.split(pattern, string, maxsplit=0, flags=0) | re.compile(pattern, flags).split(string, maxsplit) |
re.findall(pattern, string, flags=0) | re.compile(pattern, flags).findall(string) |
re.finditer(pattern, string, flags=0) | re.compile(pattern, flags).finditer(string) |
re.sub(pattern, repl, string, count=0, flags=0) | re.compile(pattern, flags).sub(repl, string, count) |
re.subn(pattern, repl, string, count=0, flags=0) | re.compile(pattern, flags).subn(repl, string, count) |
替換字符串 template 的內容,使其被轉義。並返回替換後的字符串。
除字符串的轉義之外,\1
、\g<1>
等也會被轉義。
返回指定的組所匹配的內容。
其參數可以為正整數,代表組的索引(以 1 開頭)。也可以是字符串,代表組的名稱。
如果只有一個參數,則會返回字符串。如果有多個參數,則會返回字符串組成的元組。
特別的:
- 參數 0 對應的是匹配的完整子字符串。
- 不輸入任何參數時,等同於
self.group(0)
import re
p = re.compile(r"\b(?P<attr>\w+)=(?P<value>[^ ]+)")
s = '<img src="./image.png" width=100 height=100 />'
m = p.search(s)
print(m.group()) #输出:src="./image.png"
print(m.group(0)) #输出:src="./image.png"
print(m.group(1)) #输出:src
print(m.group(1,2)) #输出:('src', '"./image.png"')
print(m.group("attr","value")) #输出:('src', '"./image.png"')
使用 self[g]
來調用此方法。
等同於 self.group(g)
。
返回包含所有匹配子組的字符串。對於總共有 n 個組的情況,等同於 self.group(1,2,...,n)
。
形如 (pattern)*
的組可能一次都不會被匹配。此時,該組對應的位置會輸出 default 的內容。
類似 groups 方法,但是返回的是 组的名称:匹配到的内容
的字典。
該屬性保存最後一個匹配的整數索引值。如果沒有匹配的組,該屬性為 None。
嵌套中的組不會被計數。比如,對於字符串 'ab',表達式 (a)b, ((a)(b)), 和 ((ab)) 將得到 lastindex == 1 , 而 (a)(b) 會得到 lastindex == 2 。
最後一個匹配的命名組名字。如果沒有匹配的組,該屬性為 None。
返回 group 所示的組,在原始字符串中起始位置的索引。
如果不輸入 group 或 group 為 0,則返回匹配開始的位置。
返回 group 所示的組,在原始字符串中結束位置的索引。
如果不輸入 group 或 group 為 0,則返回匹配結束的位置。
返回 (start([group]), end([group])) 元組。
生成 re.Match 需要用到 使用 re.compile(pattern).match(string, pos, endpos) 或 re.compile(pattern).search(string, pos, endpos) 等方法。以下屬性表示其中的參數或對象。
生成此 re.Match 實例所使用的參數 pos。
生成此 re.Match 實例所使用的參數 endpos。
生成此 re.Match 實例所使用的 re.Pattern 實例。
生成此 re.Match 實例所使用的參數 string。即被匹配的字符串。
生成一個能匹配 pattern 字符串的、正則表達式字符串。
清除正則表達式的緩存。
由於「多次匹配」的匹配機制,一些正則表達式可能會導致匹配時間隨字符串長度指數增長,比如以下是導致 Cloudflare 在 2019 年 7 月發生全球宕機事故的正則表達式的一部分:
.*(?:.*=.*)
如果用這個正則表達式來匹配形如 title=User:Xyy23330121
的內容,它會:
.*
匹配到title=User:Xyy23330121
.*
匹配到空字符串=
到字符串尾,無法匹配,回退
- 空字符串無法回退,繼續向前回退。
.*
匹配到title=User:Xyy2333012
.*
匹配到1
=
到字符串尾,無法匹配,回退
.*
匹配到空字符串=
匹配到1
,無法匹配,回退
- 空字符串無法回退,繼續向前回退。
.*
匹配到title=User:Xyy233301
- ...
從結論而言,它會嘗試匹配總共 1 + 2 + ... + 16 = 136 次才會正確匹配所有內容。而如果等號後面的內容更多,情況只會更糟。
請在設置正則表達式時儘可能慎重。