本文是速通教程,僅會介紹最基礎的知識。如需了解更多,請參考官方文檔或其他文章。 \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 可用於練習正則表達式。