個人博客文章:Python 中的正則表達式
前言
正則表達式很重要,特別是對於字符串的處理。作爲一個使用 Python 的人不會使用正則表達式的使用,到底浪費了多少時間在處理字符,天才知道啊,鑑於自己的心酸歷程,決定還是好好做做筆記,但還需要多加練習,學以致用。
正則表達式
什麼是正則表達式?
正則表達式,即 regular expression,通常簡寫爲 regex 或者 regexp, 簡單的來說就是用於查找目標文本中的特定字符串,這些特定字符串遵循正則表達式所描述的規則。因此正則表達式就是用來定義字符串組成的規則的,然後通過函數實現對規則的解釋,查找特定的字符串。
應用場景
- 提取字符串,找出符合規則的字符串,如查找文本中的形如 [email protected] 的郵箱
- 替換字符串
- 網頁信息填寫時,驗證用戶名、郵箱等是否符合網站設定的規則
基本語法一覽
以下正則表達式,可以查找到諸如 hello-regex
,regex101_cool
,110_119
等字符串,這些字符串以字母 a-z
或數字 0-9
、下劃線 _
、連字符 -
,開頭和結尾,長度在 3 到 15 之間。圖轉自 learn_regex.
基礎
元字符
正則表達式是用來描述字符串組成的規則的,那麼它如何描述呢,就像編程語言有關鍵字一樣,它也有描述規則的特殊字符,稱爲元字符,這些字符都用特殊的含義。
元字符 | 描述 |
---|---|
. |
表示一個任意字符,換行符除外 |
+ |
表示重複 >=1 次,+ 之前的字符 |
* |
表示重複 >= 0 次,* 之前的字符 |
? |
表示重複 0 或 1次,? 之前的字符 |
^ |
從字符串頭部開始匹配 |
$ |
從字符串尾部開始匹配 |
[] |
匹配方括號內的任意字符,用來自定義字符集合 |
[^] |
匹配除方括號以爲的任意字符,用來過濾字符集合 |
{m,n} |
匹配 m 到 n 個 大括號之前的字符 |
(xyz) |
完全匹配 xyz |
| |
或運算符,表示匹配或運算符前或者後面的字符 |
\ |
轉義字符,用於元字符之前,去掉特殊含義 |
簡寫字符集
正則表達式提供一些常用的字符集簡寫. 如下:
簡寫 | 描述 |
---|---|
. |
除換行符外的所有字符 |
\w |
匹配所有字母和數字字符, 等同於 [a-zA-Z0-9_] |
\W |
匹配所有非字母和非數字字符, 即符號, 等同於: [^\w] |
\d |
匹配數字: [0-9] |
\D |
匹配非數字: [^\d] |
\s |
匹配所有空格字符, 等同於: [\t\n\f\r\p{Z}] |
\S |
匹配所有非空格字符: [^\s] |
\f |
匹配一個換頁符 |
\n |
匹配一個換行符 |
\r |
匹配一個回車符 |
\t |
匹配一個製表符 |
\v |
匹配一個垂直製表符 |
\p |
匹配 CR/LF (等同於 \r\n ),用來匹配 DOS 行終止符 |
進階
標誌
標誌又稱爲模式修正符,它是用來改變正則表達式的搜索模式的,在不使用 標誌 時,搜索時,區分大小寫,且只匹配每行中的第一個符合規則的字符串,範圍爲一行,就是隻能行內搜索不能進行跨行搜索,那麼相應的 標誌 就有三個:
標誌 | 描述 |
---|---|
i |
不區分大小寫 |
g |
行內全局搜索,匹配所有結果 |
m |
跨行搜索 |
表達式內修改標誌
可以在表達式內規定字符使用特定模式。
(?im)
>>> import re
>>> regex = r'(?i)\bcat|dog'
>>> s = 'Here are cAT, cat, dog, Dog'
>>> re.findall(regex, s)
['cAT', 'cat', 'dog', 'Dog']
錨點(定位置)
錨點用於規定匹配的位置,除了 ^
、$
可以分別指定字符串的開頭和結尾,還有以下錨點:
錨點 | 描述 |
---|---|
\A |
字符串頭部 |
\Z |
字符串尾部 |
\b |
單詞分界 |
\B |
分單詞分界 |
>>> re.search(r'\bcat\b', 'The fat cat sat on the mat.') # 匹配單詞 cat
<_sre.SRE_Match object; span=(8, 11), match='cat'>
>>> re.search(r'at\B', 'The faty cat sat on the mat.') # 匹配單詞中的 at
<_sre.SRE_Match object; span=(5, 7), match='at'>
零寬度斷言(前後預查)
有時候我們需要查找有帶有前後綴的文本,但我們不需要前後綴部分,如在如下人員信息文本中提取所有國家名字
id:1
Name:Jack
Country:US
id:2
Name:Rose
Country:UK
...
這時候就可以使用 零寬度斷言。零寬度斷言就是要查找的特定字符的前後綴規則,但這些前後綴只用於約束查找,實際結果不包含這些前後綴,這大概就是爲什麼稱作零寬度(個人理解)。
零寬度斷言有四種:
符合 | 描述 |
---|---|
(?=chars) |
正先行斷言,表示必須有特定後綴 chars |
(?!chars) |
負先行斷言,表示除特定後綴 chars 以外的所有後綴均可 |
(?<=chars) |
正後發斷言,表示必須有特定前綴 chars |
(?<!chars) |
負後發斷言,表示除了特定前綴 chars 外的所有前綴均可 |
從上面的人員信息文本中查找所有國家名可以使用正後發斷言,表示國家名前面必須有前綴 Country:
/(?<=(Country:))\w*/gm
,這裏用到了標誌g
和 m
分別表示,在一行內匹配所有符合的,以及在多行匹配
分組
使用正則表達式還可以從字符串中提取子字符串,正則表達式使用 ()
成組匹配,我們可以通過定義多個組抽取字符串中的多個子字符串, 如提取郵箱地址的用戶名(@之前)和郵箱的域名(@之後),我們可以使用 ^([0-9a-zA-Z]\w*)@(\w*\.com)$
,提取出兩個 組。
在 Python 中, 使用 _sre.SRE_Match
對象的 group()
方法
>>> email = r'^([0-9a-zA-Z]\w*)@(\w*\.com)$'
>>> pemail = re.compile(email)
>>> m = pemail.match('[email protected]') # 匹配成功就會返回一個 _sre.SRE_Match 對象
>>> m.group(1)
'xman'
>>> m = pemail.match('[email protected]')
>>> m # 匹配不成功,返回 None
>>> pemail.findall('[email protected]')
[('xman', 'outlook.com')]
其實 零寬度斷言 也算是 分組的一種,只不過分組只是用來約束而已。如果不想成組返回但還是需要,成組匹配,如 findall
函數,如果存在分組,則會匹配的分組結果,這時可以使用:
(?:)
進行分組匹配,但是結果是沒有分組的
>>> re.findall('^(?:\w+)@(?:\w*\.com)$', '[email protected]')
['[email protected]']
貪婪匹配和惰性匹配
使用正則表達式進行文本匹配時,默認使用貪婪匹配模式,也就是匹配儘可能長的字符串,但是很多時候我們得不到想要的結果,如我們想要在下面文本中查找類似 xxxrose
的字符串
jack@rose is a comination of jack and rose using @
使用 /.*rose/
,匹配到 jack@rose is a comination of jack and rose
我們需要使用惰性匹配,使用 ?
切換。
使用 /.*?rose/
,匹配到 jack@rose
使用 *
、+
、?
{m,n}
時都在其後添加 ?
切換成惰性匹配模式。
Python + Regex
Python 中對字符串的處理有了 正則表達式的加持,可謂是如虎添翼,極大提高我們的對字符串處理的效率。要在 Python 使用正則表達式,需要使用 re
模塊中的函數,通常用於進行字符查找、替換、切割等。
基本流程
使用 re
模塊進行正則表達式處理,主要有兩個步驟:
- 將正則表達式,編譯成
_sre.SRE_Pattern
對象, - 使用對象的方法使用正則表達式進行匹配、查找、切割等操作。
>>> import re
>>> regex = r'\d{12}' #12個連續數字
>>> p = re.compile(regex) # 編譯表達式
>>> s = 'What dose the code 110119120114 mean'
>>> regex.search(regex) # 在字符串中查找
<_sre.SRE_Match object; span=(19, 31), match='110119120114'> # 返回一個 _sre.SRE_Match 對象
由於在 Python 中也有需要轉義的字符,就會導致需要很多轉義的情況如,如果需要匹配
“\text\"
那麼就需要對需要轉義兩次,第一次轉義是因爲在正則表達式中\
是元字符,第二次轉義是因爲\
在 Python 中也需要轉義,最終的正則表達式爲”\\\\test\\\\"
,爲了避免麻煩,直接使用 Python 字符串的r
前綴,就不需要考慮 Python 的轉義了,但是 正則表達式中的元字符仍需要轉義的,可簡寫爲r”\\test\\
Match 對象
字符匹配後通常會以返回一個 Match 對象,匹配的結果存在 Match 對象中,主要方法有:
方法/屬性 | 描述 |
---|---|
group([id=0]) |
返回進行匹配的整個字符串,默認參數爲0,分組id爲1,2,, |
start() |
返回匹配的起始位置 |
end() |
返回匹配的結束位置 |
span() |
返回(start, end) ,匹配位置的 tuple |
>>> regex = r'\d{11}(?=\W)' # 找 11 位的數字
>>> nums = '12213313451334,566778,13315551666,'
>>> p = re.compile(regex)
>>> p.search(nums)
<_sre.SRE_Match object; span=(3, 14), match='13313451334'>
>>> result.group()
'13313451334'
>>> result.start()
3
標誌
正則表達式中的標誌 | re 模塊中的標誌 |
---|---|
i |
re.I |
m |
re.M |
s ,用於使得 . 可以表示任意字符,包括換行符 |
re.S |
x ,用於使得 正則表達式 可以換行和添加註釋 |
re.X |
常用的函數
re.compile
(pattern, flags=0)
將一個正則表達式編譯成 正則表達式 對象,以便於重複使用,該匹配模式。
# 查找
import re
regex = re.complie(pattern)
result = regex.match(string)
# 等同於
result = re.match(pattern, string)
正則表達式的處理流程:
所有的正則表達式都需要顯示或者隱式的編譯成 正則表達式 對象,才能進一步使用正則表達式去匹配字符串,因此對於需要重複使用的模式,應該一次性進行編譯。
re.match
(pattern, string, flags=0) | pattern.match
(string[, pos[, endpos]])
以字符串頭部爲匹配的起點,匹配模式,如果存在匹配,返回一個 Match
對象,如果不存在匹配返回 None
。
re.search
(pattern, string, flags=0) | pattern.search
(string[, pos[, endpos]])
從字符任意位置匹配模式,如果存在匹配,返回一個 Match
對象,如果不存在匹配返回 None
。
match
和search
區別在於,search
可以在字符串的任意位置進行匹配,而match
默認從頭開始匹配。re.match('s', 'test') # 返回 None,字符串頭部不匹配 re.match('t', 'test') # 返回 Match 對象 re.search('s', 'test') # 返回 Match 對象,在字符串中任意匹配
re.fullmatch
(pattern, string, flags=0) | pattern.fullmatch
(string[, pos[, endpos]])
如何整個字符串符合模式,返回一個 Match 對象,不匹配返回 None。
re.findall
(pattern, string, flags=0) | pattern.findall
(string[, pos[, endpos]])
返回字符串中所有匹配的子字符串,返回一個 List 對象。
起到了正則表達式中的
g
標誌的作用,故 python 的re
模塊沒有該標誌
re.finditer
(pattern, string, flags=0) | pattern.finditer
(string[, pos[, endpos]])
返回生成 Match 對象的迭代器。
>>> import re
>>> regex = r'\w+@\w+\.com'
>>> p = re.compile(regex)
>>> s = '[email protected],[email protected],[email protected]'
>>> res = p.finditer(s)
>>> for r in res:
print(r[0])
#輸出
pig@dog.com
list@tuple.com
2@111.com
re.split
(pattern, string, maxsplit=0, flags=0) | pattern.split
(string[, pos[, endpos]])
根據模式將字符串進行拆分。
>>> s = '123,333, 155--134-,were&here,we|--are-you' # 以非數字、非字母不包括 -,以及2個以上的 - 爲分隔符
>>> regex = r'\s|-{2,}|[^\w-]'
>>> re.split(regex, s)
['123', '333', '', '155', '134-', 'were', 'here', 'we', '', 'are-you']