本文是速通教程,僅會介紹最基礎的知識。如需了解更多,請參考官方文檔或其他文章。 \textcolor{red}{\text{本文是速通教程,僅會介紹最基礎的知識。如需了解更多,請參考官方文檔或其他文章。}} 本文是速通教程,僅會介紹最基礎的知識。如需了解更多,請參考官方文檔或其他文章。
什麼是正則表達式?正則表達式(regular expression)是一種特殊的字符串,它能幫助你方便的檢查一個字符串是否與給定的模式匹配。Python 中的 re
模塊提供了正則表達式的全部功能,在接下來的幾章,我們將詳細介紹 re
中最為常用的功能。
import re
re.match
嘗試從字符串的起始位置匹配一個模式,如果匹配成功,則會返回一個 re.Match
對象;如果匹配失敗,則返回 None
。具體格式如下:
re.match(pattern, string, flags=0)
pattern
:正則表達式;string
:要匹配的字符串;flags
:修飾符,用於控制匹配模式。正則表達式既可以包含普通字符也可以包含特殊字符。如果僅包含普通字符,那就是匹配某個特定的字符串:
""" 嘗試從字符串 abcde 的起始位置匹配字符串 abc """
s = re.match('abc', 'abcde')
print(s)
# <re.Match object; span=(0, 3), match='abc'>
字符串 abc
在字符串 abcde
中的起始位置和終止位置分別為 0
和 2
,根據左閉右開原則,span
為 (0, 3)
。
如果要獲得匹配的結果,則可使用 group
方法:
print(s.group())
# abc
注意,以下這樣的匹配會失敗,因為 match
是從字符串的起始位置進行匹配的:
s = re.match('bcd', 'abcde')
print(s)
# None
常用特殊字符(special characters)列在下表中:
.
匹配除了換行符 \n
以外的任意單個字符^
匹配起始位置$
匹配終止位置(換行符之前)*
表示 *
前的一個字符可以出現 0 次或任意多次+
表示 +
前的一個字符可以出現 1 次或任意多次?
表示 ?
前的一個字符可以出現 0 次或 1 次{m}
表示 {m}
前的一個字符出現 m 次{m,}
表示 {m,}
前的一個字符可以出現 m 次及以上{,n}
表示 {,n}
前的一個字符最多出現 n 次{m,n}
表示 {m,n}
前的一個字符出現 m 次到 n 次[]
匹配 []
中列舉出的字符()
匹配 ()
內的表達式,表示一個分組|
或為簡便起見,我們定義一個 match
函數用來更為直觀地展示匹配結果:
def match(pattern, list_of_strings):
for string in list_of_strings:
if re.match(pattern, string):
print('匹配成功!結果為:', res.group())
else:
print('匹配失敗!')
.
:
match('.', ['a', 'ab', 'abc'])
# 匹配成功!結果為: a
# 匹配成功!結果為: a
# 匹配成功!結果為: a
因為我們是從頭開始匹配單個字符的,所以結果均為 a
。
^
、$
:
match('^ab', ['ab', 'abc', 'adc', 'bac'])
# 匹配成功!結果為: ab
# 匹配成功!結果為: ab
# 匹配失敗!
# 匹配失敗!
match('cd$', ['cd', 'acd', 'adc', 'cdcd'])
# 匹配成功!結果為: cd
# 匹配失敗!
# 匹配失敗!
# 匹配失敗!
看似是匹配以 cd
為結尾的字符串,但實際上別忘了 match
是從字符串的起始位置開始匹配的,因此上述語句實際上就是匹配字符串 cd
。
*
、+
、?
:
match('a*', ['aa', 'aba', 'baa', 'aaaa'])
# 匹配成功!結果為: aa
# 匹配成功!結果為: a
# 匹配成功!結果為:
# 匹配成功!結果為: aaaa
match('a+', ['aa', 'aba', 'baa'])
# 匹配成功!結果為: aa
# 匹配成功!結果為: a
# 匹配失敗!
match('a?', ['aa', 'ab', 'ba'])
# 匹配成功!結果為: a
# 匹配成功!結果為: a
# 匹配成功!結果為:
注意,a*
代表 a
可以出現 0 次或任意多次,因此 baa
去匹配會得到空字符串。
{m}
、{m,}
、{,n}
、{m,n}
(注意沒有空格):
match('a{3}', ['abaa', 'aaab', 'baaa'])
# 匹配失敗!
# 匹配成功!結果為: aaa
# 匹配失敗!
match('a{3,}', ['aaab', 'aaaab', 'baaa'])
# 匹配成功!結果為: aaa
# 匹配成功!結果為: aaaa
# 匹配失敗!
match('a{,3}', ['aaab', 'aaaab', 'baaa'])
# 匹配成功!結果為: aaa
# 匹配成功!結果為: aaa
# 匹配成功!結果為:
match('a{3,5}', ['a' * i for i in range(2, 7)])
# 匹配失敗!
# 匹配成功!結果為: aaa
# 匹配成功!結果為: aaaa
# 匹配成功!結果為: aaaaa
# 匹配成功!結果為: aaaaa
[]
:
match('[123]', [str(i) for i in range(1, 5)])
# 匹配成功!結果為: 1
# 匹配成功!結果為: 2
# 匹配成功!結果為: 3
# 匹配失敗!
注意,我們可以將 [123]
簡寫為 [1-3]
,這意味著若要匹配單個數字,則可以采用 [0-9]
這樣的正則表達式:
match('[0-9]', ['a', 'A', '1', '3', '_'])
# 匹配失敗!
# 匹配失敗!
# 匹配成功!結果為: 1
# 匹配成功!結果為: 3
# 匹配失敗!
更進一步,如果我們要想匹配區間 [ 1 , 35 ] [1, 35] [1,35] 內的所有整數,該如何做呢?很自然的一個想法是使用 [1-35]
,但仔細觀察下面的例子:
match('[1-35]', ['1', '2', '3', '4'])
# 匹配成功!結果為: 1
# 匹配成功!結果為: 2
# 匹配成功!結果為: 3
# 匹配失敗!
會發現數字 4 4 4 匹配失敗了。這是因為 -
只能連接相鄰的兩個數字,所以 [1-35]
實際上代表數字 1
、2
、3
和 5
。也就是說,除了這四個數字以外的數全部都會匹配失敗。
我們分三種情形考慮:十位數為 3 3 3,十位數為 1 1 1 或 2 2 2,只有個位數(需要使用或運算 |
)
pattern = '3[0-5]|[12][0-9]|[1-9]'
該正則表達式的確能夠全部正確匹配 [ 1 , 35 ] [1,35] [1,35] 內的所有整數,但是:
match('3[0-5]|[12][0-9]|[1-9]', ['36', '350'])
# 匹配成功!結果為: 3
# 匹配成功!結果為: 35
我們會發現區間之外的數也能夠匹配成功。因此需要使用 $
來防止誤判,正確做法是:
pattern = '(3[0-5]|[12][0-9]|[1-9])$'
其中 ()
的作用之後會提及(這裡可以粗略地理解成視為一個整體)。
除此之外,我們還可以判斷給定的字符是否是字母,相應的正則表達式為 [a-zA-Z]
:
match('[a-zA-Z]', ['-', 'a', '9', 'G', '.'])
# 匹配失敗!
# 匹配成功!結果為: a
# 匹配失敗!
# 匹配成功!結果為: G
# 匹配失敗!
如果我們想要匹配非數字字符,則需要使用 ^
,它表示取補集:
match('[^0-9]', ['-', 'a', '3', 'M', '9', '_'])
# 匹配成功!結果為: -
# 匹配成功!結果為: a
# 匹配失敗!
# 匹配成功!結果為: M
# 匹配失敗!
# 匹配成功!結果為: _
()
:
""" 匹配多個 ab """
match('(ab)+', ['ac', 'abc', 'abbc', 'abababac', 'adc'])
# 匹配失敗!
# 匹配成功!結果為: ab
# 匹配成功!結果為: ab
# 匹配成功!結果為: ababab
# 匹配失敗!
注意 ab+
這樣的正則表達式是無效的,它代表只有一個字符 a
和一個及以上的字符 b
。因此我們必須用 ()
將其括起來視為一個整體,也稱作一個分組。
以 \
開頭並僅連一個字符的稱為特殊序列(special sequences),常用特殊序列列在下表中:
\d
等價於 [0-9]
,即所有數字(巧記:digit)\D
等價於 [^\d]
,即所有非數字\s
空格字符(巧記:space)\S
等價於 [^\s]
,即所有非空格字符\w
等價於 [a-zA-Z0-9_]
,即所有單詞字符,包括字母、數字和下劃線(巧記:word)\W
等價於 [^\w]
,即所有非單詞字符""" 示例一 """
match('\d', ['1', 'a', '_', '-'])
# 匹配成功!結果為: 1
# 匹配失敗!
# 匹配失敗!
# 匹配失敗!
match('\D', ['1', 'a', '_', '-'])
# 匹配失敗!
# 匹配成功!結果為: a
# 匹配成功!結果為: _
# 匹配成功!結果為: -
""" 示例二 """
match('\s', ['1', 'a', '_', ' '])
# 匹配失敗!
# 匹配失敗!
# 匹配失敗!
# 匹配成功!結果為:
match('\S', ['1', 'a', '_', ' '])
# 匹配成功!結果為: 1
# 匹配成功!結果為: a
# 匹配成功!結果為: _
# 匹配失敗!
""" 示例三 """
match('\w', ['1', 'a', '_', ' ', ']'])
# 匹配成功!結果為: 1
# 匹配成功!結果為: a
# 匹配成功!結果為: _
# 匹配失敗!
# 匹配失敗!
match('\W', ['1', 'a', '_', ' ', ']'])
# 匹配失敗!
# 匹配失敗!
# 匹配失敗!
# 匹配成功!結果為:
# 匹配成功!結果為: ]
接下來我們通過一些例子來進一步鞏固之前所學的概念。
十六進制的顏色值的格式通常為 #XXXXXX
,其中 X
的取值可以為數字,也可以為 A-F
中的任意字符(假設這裡不考慮小寫情形)。
regex = '#[A-F0-9]{6}$'
colors = ['#00', '#FFFFFF', '#FFAAFF', '#00HH00', '#AABBCC', '#000000', '#FFFFFFFF']
match(regex, colors)
# 匹配失敗!
# 匹配成功!結果為: #FFFFFF
# 匹配成功!結果為: #FFAAFF
# 匹配失敗!
# 匹配成功!結果為: #AABBCC
# 匹配成功!結果為: #000000
# 匹配失敗!
我們不考慮前綴0的情形,例如對於數字 8
、35
,諸如 08
、008
、035
這樣的形式是排除在外的。
只需分別考慮三位數、兩位數、一位數的情形:
regex = '(100|[1-9]\d|[1-9])$'
numbers = ['0', '5', '05', '005', '12', '012', '89', '100', '101']
match(regex, numbers)
# 匹配失敗!
# 匹配成功!結果為: 5
# 匹配失敗!
# 匹配失敗!
# 匹配成功!結果為: 12
# 匹配失敗!
# 匹配成功!結果為: 89
# 匹配成功!結果為: 100
# 匹配失敗!
這裡我們自創一個郵箱,假設域名為 sky.com
。在創建新用戶時,要求用戶名只能由數字、字母及下劃線組成,且不能以下劃線開頭,郵箱名長度在6-18位。我們該如何用正則表達式來判斷用戶輸入的郵箱是否符合規范呢?
regex = '[a-zA-Z0-9][\w]{5,17}@sky\.com$'
emails = [
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]',
]
match(regex, emails)
# 匹配失敗!
# 匹配失敗!
# 匹配失敗!
# 匹配失敗!
# 匹配成功!結果為: [email protected]
# 匹配失敗!
# 匹配失敗!
如果 sky
郵箱允許用戶開通vip,開通後郵箱域名變為了 vip.sky.com
,且普通用戶和vip用戶均屬於 sky
郵箱用戶。該情形下的正則表達式需要改寫為:
regex = '[a-zA-Z0-9][\w]{5,17}@(vip\.)?sky\.com$'
emails = [
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]',
]
match(regex, emails)
# 匹配成功!結果為: [email protected]
# 匹配成功!結果為: [email protected]
# 匹配失敗!
# 匹配失敗!
IPV4的格式通常為 X.X.X.X
,其中 X
的范圍為 0-255。這裡依然不考慮前綴0的情形。
regex = '((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$'
ipv4s = [
'0.0.0.0',
'0.0.255.0',
'255.0.0',
'127.0.0.1.0',
'256.0.0.123',
'255.255.255.255',
'012.08.0.0',
]
match(regex, ipv4s)
# 匹配成功!結果為: 0.0.0.0
# 匹配成功!結果為: 0.0.255.0
# 匹配失敗!
# 匹配失敗!
# 匹配失敗!
# 匹配成功!結果為: 255.255.255.255
# 匹配失敗!
修飾符即 re.match
函數中的 flags
參數,常用的修飾符列在下表中:
re.I
匹配時忽略大小寫re.S
使得 .
能夠匹配任意單個字符(包括換行符 \n
)s = re.match('a', 'A', flags=re.I)
print(s.group())
# A
s = re.match('.', '\n', flags=re.S)
print(s)
# <re.Match object; span=(0, 1), match='\n'>
re.match
是從字符串的起始位置開始匹配,即只要匹配成功,則無需管字符串的剩余位置。而 re.fullmatch
則是匹配整個字符串。
格式如下:
re.fullmatch(pattern, string, flags=0)
例如:
print(re.match('\d', '3a'))
# <re.Match object; span=(0, 1), match='3'>
print(re.fullmatch('\d', '3a'))
# None
re.search
從左到右掃描整個字符串並返回第一個成功的匹配。如果匹配失敗,則返回 None
。
格式如下:
re.search(pattern, string, flags=0)
例如:
print(re.search('ab', 'abcd'))
# <re.Match object; span=(0, 2), match='ab'>
print(re.search('cd', 'abcd'))
# <re.Match object; span=(2, 4), match='cd'>
re.findall
是在給定的字符串中找到所有匹配正則表達式的子串,並以列表的形式返回,格式如下:
re.findall(pattern, string, flags=0)
例如:
res = re.findall('\d+', 'ab 13 cd- 274 .]')
print(res)
# ['13', '274']
res = re.findall('(\w+):(\d+)', 'Xiaoming:16, Xiaohong:14')
print(res)
# [('Xiaoming', '16'), ('Xiaohong', '14')]
re.sub
用於將匹配到的子串替換為另一個子串,執行時從左向右進行替換。格式如下:
re.sub(pattern, repl, string, count=0, flags=0)
repl
代表替換後的字符串,count
是最大替換次數,0表示替換所有。
例如:
res = re.sub(' ', '', '1 2 3 4 5')
print(res)
# 12345
res = re.sub(' ', '', '1 2 3 4 5', count=2)
print(res)
# 123 4 5
re.split
將根據匹配切割字符串(從左向右),並返回一個列表。格式如下:
re.split(pattern, string, maxsplit=0, flags=0)
count
是最大切割次數,0表示切割所有位置。
例如:
res = re.split(' ', '1 2 3 4 5')
print(res)
# ['1', '2', '3', '4', '5']
res = re.split(' ', '1 2 3 4 5', maxsplit=2)
print(res)
# ['1', '2', '3 4 5']
所有的量詞:*
、+
、?
、{m}
、{m,}
、{,n}
、{m,n}
默認采取貪婪匹配的原則,即在匹配成功的情況下盡可能多地匹配。
以 *
為例,顯然 \d*
是匹配任意長度的數字:
match('\d*', ['1234abc'])
# 匹配成功!結果為: 1234
根據貪婪原則,最終匹配結果一定是 1234
。
有些時候,我們不想盡可能多地匹配,而是盡可能少地匹配。這時候我們可以在量詞後面加上 ?
,它表示非貪婪匹配:
match('\d*?', ['1234abc'])
# 匹配成功!結果為:
在非貪婪模式下,\d
應該出現0次(盡可能少匹配),於是最終返回空字符。
我們再來看一下其他量詞的情況:
match('\d+?', ['1234abc'])
# 匹配成功!結果為: 1
match('\d??', ['1234abc'])
# 匹配成功!結果為:
match('\d{2,}?', ['1234abc'])
# 匹配成功!結果為: 12
match('\d{2,5}?', ['1234abc'])
# 匹配成功!結果為: 12
match('\d{,5}?', ['1234abc'])
# 匹配成功!結果為:
regex101 可用於練習正則表達式。