本文首發於公眾號:Hunter後端
原文連結:Python筆記五之正規表示式
這一篇筆記介紹在 Python 裡使用正規表示式。
正規表示式,Regular Expression,可用於在一個目標字串裡對於指定模式的字元進行查詢、替換、分割等操作。
比如,判斷某個字串裡是否都是數字,或者是否包含指定字串,又或者更直接的例子是判斷電話號碼或者郵箱是否合法等。
這一篇筆記裡,我們將先介紹一個正規表示式的函式,並以此來引入正規表示式的各種模式,並輔以各個例子進行介紹。
u1s1,正規表示式,我學了三四遍,學一遍忘一遍,忘一遍學一遍,只要隔一陣子不用就會忘,所以這一篇筆記力求將正規表示式的所有模式和用法都記錄下來,用作之後的查詢筆記。
以下筆記使用 Python 版本是 3.8。
以下是本篇筆記目錄:
- re.findall
表示式語法
匹配字串型別
- \b-匹配空字串,但只在單詞開始或結尾
- \B-匹配空字串,不能在開頭或結尾
- \d-匹配十進位制數字
- \D-匹配非十進位制字元
- \s-匹配空白字元
- \S-匹配非空白字元
- \w-匹配字元
- \W-匹配非單詞字元
- 匹配字串出現位置
1) \A-只匹配字串開始
2) \Z-只匹配字串結尾
3) ^-匹配字串開頭
4) $-匹配字串結尾 匹配字串數量
1) *-匹配重複 0 到 n 次
2) +-匹配重複 1 到 n 次
3) ?-匹配重複 0 到 1 次
4) {}-指定匹配重複次數a. {m}-只匹配重複 m 次 b. {m,n}-匹配重複 m 到 n 次
- 匹配字串集合
1) 字元可以單獨列出
2) 可以表示字元範圍
3) 特殊字元在集合裡只會匹配其原始字元含義
4) 字元類 \s 或 \w 可以在集合裡使用
5) 取反操作可以使用 ^ 其他匹配型別
- |-表示式的或操作
- ()-匹配括號內的任意正規表示式
常用正則方法
- re.search
- re.match
- re.split
- re.findall
- re.finditer
- re.sub
- re.subn
- re.compile
- 其他引數
1) re.I
2) re.M
re.Match 匹配物件
- Match.group()
Match.__getitem__(g)
- Match.groups()
- Match.re
- Match.string
- Match.start() 和 Match.end()
- Match.span()
1、re.findall
使用正規表示式,首先要引入模組:
import re
這裡從 re.findall
開始介紹,findall 方法表示的是找到目標字串裡符合指定模式的全部資料。
比如我們有一個字串 abcdefg
,想要從其中找到 de
,就可以如下操作:
str_1 = "abcdefg"
target_str = "de"
print(re.findall(target_str, str_1))
返回的就是一個列表,列表元素是我們的目標字串 de
:
['de']
我們的 target_str 就是一個匹配模式,這裡是一個純字串,我們可以將其替換成其他的模式字串。
接下來我們將分類介紹正規表示式語法。
2、表示式語法
正規表示式有很多種,比如表示匹配數量的 +
,*
,?
,還有表示匹配字串內容的 \s
,\w
等。
下面將從這些類別分別開始介紹:匹配字串型別、匹配字串出現位置、匹配字串數量、匹配字串集合等
1. 匹配字串型別
1) \b-匹配空字串,但只在單詞開始或結尾
\b 可以匹配空字串,但是隻在字串開始或者結尾的位置,這裡的空字串涵蓋了標籤符號,比如 , . ,。?
等,也包括換行製表符 \n \t
等,也包含 '',可以理解為字串的邊界部分。
所以 \b
的作用其實就可用於匹配特定字串的字首或者字尾,這裡的字首和字尾並不僅僅是指整個字串的字首和字尾,也包括字串內部被分割的字首和字尾。
比如對於下面這個字串:
i have a apple
我們想找到是否有 ha
開頭的單詞,可以如此操作:
str_1 = "i have a apple"
target_pattern = r"\bha"
print(re.findall(target_pattern, str_1))
字串如果是以下幾種情況,也可以匹配上:
str_1 = "i ,have a apple"
str_1 = "i \thave a apple"
str_1 = "i ha"
我們還可以使用 \b 來匹配特定的單詞,在英文中,單詞的出現是前後都有空格或者標點符號的,那麼我們就可以前後都加上 \b 來限定匹配是否出現過此單詞:
str_1 = "i have an apple"
str_1 = "i have an apple, "
str_1 = "i have an apple how are you"
target_pattern = r"\bapple\b"
print(re.findall(target_pattern, str_1))
2) \B-匹配空字串,不能在開頭或結尾
\B 是 \b 的取非操作,含義是匹配空字串,但是不能出現在開頭或者結尾,也就是說 \B 所在的位置必須有 1 至多個非空字串來替代:
str_1 = "i have an apple"
target_pattern = r"app\B"
print(re.findall(target_pattern, str_1))
3) \d-匹配十進位制數字
\d 用來匹配十進位制數字,也就是 0-9 這些,比如下面的操作:
str_1 = "asdas98123asa978d"
target_pattern = r"\d"
print(re.findall(target_pattern, str_1))
# ['9', '8', '1', '2', '3', '9', '7', '8']
可以看到返回的結果是分隔開的數字,如果想要他們在一起返回,我們可以使用 \d+
來操作,+
表示的匹配 1 到 n 次,這個後面再介紹。
4) \D-匹配非十進位制字元
\D 表示的是 \d 相反的操作,非十進位制字元,可以使用上面的示例進行測試。
5) \s-匹配空白字元
\s 匹配的空白字元不包括標點符號,常見的有換行符,製表符,回車符等跳脫字元,\n \t \r \f
等
str_1 = "asdas9812\v3a\rs,.\ta9\n78\fd"
target_pattern = r"\s"
print(re.findall(target_pattern, str_1))
6) \S-匹配非空白字元
\S 是 \s 取非操作,除了上面的換行符、製表符等字元外,包括標點符號皆可被匹配上
7) \w-匹配字元
\w 不匹配換行符、製表符等跳脫字元,不匹配中英文常見標點符號,比如 , . ; "
等,但是可以匹配中英文字元、數字和下劃線,比如下面的示例:
str_1 = "asd—— _a你‘'好,s9。8?12\v3a\rs,.\ta9\n78\fd"
target_pattern = r"\w"
print(re.findall(target_pattern, str_1))
# ['a', 's', 'd', '_', 'a', '你', '好', 's', '9', '8', '1', '2', '3', 'a', 's', 'a', '9', '7', '8', 'd']
8) \W-匹配非單詞字元
\W 是 \w 的取反操作。
2. 匹配字串出現位置
前面介紹的匹配字串的型別,這裡介紹匹配出現的位置,比如開頭或者結尾。
1) \A-只匹配字串開始
\A 只匹配字串的開始,也就是我們所說的字串字首:
str_1 = "asd—— _a你‘'好,\ns9。8?12\v3a\rs,.\ta9\n78\fd"
target_pattern = r"\Aasd"
print(re.findall(target_pattern, str_1))
與字串的 startswith() 函式在匹配功能上是一樣的。
2) \Z-只匹配字串結尾
\Z 只匹配字串結尾部分,也就是所說的字串字尾:
str_1 = "asd—— _a你‘'好,\ns9。8?12\v3a\rs,.\ta9\n78d"
target_pattern = r"d\Z"
print(re.findall(target_pattern, str_1))
3) ^-匹配字串開頭
^ 也是隻匹配字串的開頭,但是與 \A 不同的是,使用 re.M 模式下,也可以匹配換行符後的開頭,比如下面的示例,就可以返回兩個結果:
str_1 = "asd—— _\na你‘'好,\ns9。8?12\v3a\rs,.\ta9\n78d"
target_pattern = r"^a"
print(re.findall(target_pattern, str_1, re.M))
# ['a', 'a']
如果去掉 re.M,則會退化成 \A 的功能
4) $-匹配字串結尾
$ 也是隻匹配字串的結尾,但是在 re.M 模式下,也可以匹配換行符後新一行的結尾,比如下面的示例,可以返回兩個結果:
str_1 = "asd—— _\na你‘'好,\ns9。8?12\v3a\rs,.\ta9d\n78d"
target_pattern = r"d$"
print(re.findall(target_pattern, str_1, re.M))
同理,如果去掉 re.M,則會退化成 \Z 的功能。
3. 匹配字串數量
除了匹配指定模式的內容,字元位置,我們還可以匹配指定模式的數量,比如想只要滿足一個即可返回,或者儘可能多的將滿足的字元返回等。
接下來一一介紹如何匹配字串數量。
1) *-匹配重複 0 到 n 次
*
表示匹配的數量可以重複從 0 到 n 次,這個 n 為任意次,儘量多的匹配字串。
比如我們想匹配 a
以及 a
後面可以加上任意個 b
字元,比如希望 a
,ab
,abb
,abbbb
等都可以被匹配上。
那麼就可以使用下面的操作:
str_1 = "axxxxabbxxxxabbbb"
target_pattern = r"ab*"
print(re.findall(target_pattern, str_1))
# ['a', 'abb', 'abbbb']
2) +-匹配重複 1 到 n 次
+
表示匹配的數量可以重複從 1 到 n 次,提前條件是匹配模式必須出現一個。
還是上面的例子,我們希望可以匹配上 ab
,abbb
,以及無限多個 b
,但是不可匹配 a
,可以如下操作:
str_1 = "axxxxabbxxxxabbbb"
target_pattern = r"ab+"
print(re.findall(target_pattern, str_1))
# ['abb', 'abbbb']
3) ?-匹配重複 0 到 1 次
?
表示匹配的數量只能出現 0 到 1 次。
比如對於一個字串,我們想匹配 apple
這個單詞,但是我們也想使 apple
的複數形式 apples
也能被匹配上,所以我們這裡的 s
希望它出現的次數是 0 到 1 次
str_1 = "i have an apple, he has two apples, she has three apples"
target_pattern = r"apples?"
print(re.findall(target_pattern, str_1))
# ['apple', 'apples', 'apples']
4) {}-指定匹配重複次數
使用花括號可以指定重複的次數,可以是一個固定的值,也可以是一個範圍,下面分別介紹一下。
a. {m}-只匹配重複 m 次
{m}
表示匹配重複次數為 m,比如我們想要匹配 abbb
,也就是 a
字元後重復出現三個 b
,可以如下操作:
str_1 = "abbxxxxabbbbaxxxabbb"
target_pattern = r"ab{3}"
print(re.findall(target_pattern, str_1))
b. {m,n}-匹配重複 m 到 n 次
{m,n}
表示匹配重複次數為 m 到 n 次,比如我們想要 a
後面跟著 3,4,5個 b
都可以接受,可以如下操作:
str_1 = "abbbxxxxabbbbbbaxxxabbbb"
target_pattern = r"ab{3,5}"
print(re.findall(target_pattern, str_1))
# ['abbb', 'abbbbb', 'abbbb']
4. 匹配字串集合
我們可以使用中括號 []
來限定字串或者匹配模式的集合,也就是說我們可以將我們想要匹配的字串或者型別都加到 []
裡,滿足條件的都可以被匹配返回。
1) 字元可以單獨列出
如果我們想匹配某些單個字元,可以單獨列出來操作,比如 a, t, w, q,可以使用 [atwq],以下是示例:
str_1 = "asdqweasdaterq"
target_pattern = r"[atwq]"
print(re.findall(target_pattern, str_1))
# ['a', 'q', 'w', 'a', 'a', 't', 'q']
2) 可以表示字元範圍
我們可以使用 -
來表示字元的範圍進行匹配,比如 a-z
,或者數字型別的 0-9
,比如下面的操作:
str_1 = "asdqweasdaterq"
target_pattern = r"[a-j]"
print(re.findall(target_pattern, str_1))
# ['a', 'd', 'e', 'a', 'd', 'a', 'e']
str_1 = "asd136q78w9ea95sd6ater"
target_pattern = r"[4-9]"
print(re.findall(target_pattern, str_1))
# ['6', '7', '8', '9', '9', '5', '6']
注意: 在這裡的連線符 -
是表示範圍的,如果只是想匹配 -
字元,需要使用 \
進行轉義,或者將 -
放在首位或者末位:
str_1 = "asd136q-78w-9e-a95sd6zater"
target_pattern = r"[a\-z]"
print(re.findall(target_pattern, str_1))
# ['a', '-', '-', '-', 'a', 'z', 'a']
上面的這個操作表示的是希望匹配上 -
a
和 z
三個字元。
3) 特殊字元在集合裡只會匹配其原始字元含義
特殊字元,比如前面表示數量的 *
+
等字元在中括號裡就匹配的是對應的星號和加號:
str_1 = "asdas*adas+asds(das)dasd"
target_pattern = r"[*+()]"
print(re.findall(target_pattern, str_1))
# ['*', '+', '(', ')']
4) 字元類 \s 或 \w 可以在集合裡使用
比如下面的操作,可以 \W 和 0-9 之間的字元:
str_1 = "asdas*adas+asds(90123das)dasd"
target_pattern = r"[\W0-9]"
print(re.findall(target_pattern, str_1))
# ['*', '+', '(', '9', '0', '1', '2', '3', ')']
5) 取反操作可以使用 ^
如果要取反,意思是集合裡的匹配模式都不匹配,比如我們想匹配字串裡的非數字,可以如下操作:
str_1 = "asdas*adas+asds(90123das)dasd"
target_pattern = r"[^\d]"
print(re.findall(target_pattern, str_1))
# ['a', 's', 'd', 'a', 's', '*', 'a', 'd', 'a', 's', 'a', 's', 'd', 's', '(', 'd', 'a', 's', ')', 'd', 'a', 's', 'd']
5. 其他匹配型別
1) |-表示式的或操作
比如我們有兩個表示式 A 和 B,只要有一個符合條件即可,即匹配模式的或操作:
re.findall(r"\d+|[a-z]+", "asdas234asd2341")
# ['asdas', '234', 'asd', '2341']
2) ()-匹配括號內的任意正規表示式
小括號表示分組,可以將匹配模式分組放到多個小括號內進行匹配,匹配後的結果也會以元組的形式分組返回。
比如我們想匹配 英文字母+數字
的形式,並且以括號的形式將其分隔開,那麼返回的匹配結果也會以元組的形式將其返回:
str_1 = "asdas9872"
target_pattern = r"([a-z]+)(\d+)"
result = re.findall(target_pattern, str_1)print(result)
# [('asdas', '9872')]
如果是匹配上了多個結果,那麼多個結果會以列表的形式返回,元素也是匹配的結果以元組的形式返回:
str_1 = "asdas9872asdasklqw8267"
target_pattern = r"([a-z]+)(\d+)"
result = re.findall(target_pattern, str_1)print(result)
# [('asdas', '9872'), ('asdasklqw', '8267')]
3、常用正則方法
前面介紹了 re.findall() 方法,返回的是一個列表,元素是所有匹配上的結果,接下來介紹一些其他常用的正則方法。
1. re.search
re.search() 方法的作用是掃描整個目標字串,找到匹配上的第一個位置,然後返回一個匹配物件,如果沒有滿足要求的資料,則返回 None。
這裡要注意三點,一點是掃描整個字串,直到找到匹配的物件,另一點是找到一個符合條件的字串以後就停止掃描,即便後面還有符合條件的,三是返回一個匹配物件,關於匹配物件下面再介紹。
比如下面的示例是匹配 英文字母+數字:
str_1 = "..()-+/?asdas9872asdasklqw8267"
target_pattern = r"[a-z]+\d+"
result = re.search(target_pattern, str_1)
# <re.Match object; span=(8, 17), match='asdas9872'>
可以看到這裡,其實有多個符合匹配模式的資料,如果這裡使用 re.findall() 會返回多個值,但這裡返回的只是字串裡第一個符合條件的資料。
至於如何獲取返回的這個 re.Match 物件詳情資料,見第四節 re.Match 匹配物件
,建議先閱讀該章節再往後讀。
2. re.match
re.match() 方法也是用於匹配指定模式,效果上與 re.search() 無異,唯一不同的一點是 match() 方法只能從字串的頭部開始匹配,返回的也是一個 re.Match 物件。
比如下面的操作,第一種形式匹配不到資料,返回 None,第二種就可以返回 re.Match 物件:
str_1 = "..()-+/?asdas9872asdasklqw8267"
target_pattern = r"[a-z]+\d+"
result = re.match(target_pattern, str_1)
# None
str_1 = "asdas9872asdasklqw8267"
target_pattern = r"[a-z]+\d+"
result = re.match(target_pattern, str_1)
# <re.Match object; span=(0, 9), match='asdas9872'>
3. re.split
根據匹配模式將指定字串進行分割,在效果上相當於 string.split() 的增強版。
因為 string.split() 不能使用正則物件來對字串進行切割,而 re.split() 可以實現。
比如我們想要根據 1,2,3 對指定字串進行切割,就可以用到 |
這個操作:
re.split(r"1|2|3", "as2da1s3asdsa")
# ['as', 'da', 's', 'asdsa']
我們也可以使用其他正則物件,比如我們想要根據字串中的標點符號,空格等對字串進行切割:
re.split(r"\W+", "i have an apple, you have two apples!")
# ['i', 'have', 'an', 'apple', 'you', 'have', 'two', 'apples', '']
re.split() 還可以接受 maxsplit 引數,表示最多切割的次數:
re.split(r"\W+", "i have an apple, you have two apples!", maxsplit=2)
# ['i', 'have', 'an apple, you have two apples!']
4. re.findall
re.findall() 前面有過介紹,根據匹配模式獲取所有的匹配字元,結果以列表形式返回,元素為匹配的字串:
re.findall(r"\d+", "asd12asxda45asd456sd23")
# ['12', '45', '456', '23']
5. re.finditer
re.finditer() 函式與 re.findall() 函式效果類似,都是找到目標字串裡全部滿足條件的字串,但是不同的是 finditer() 返回的一個迭代器,迭代器儲存的是匹配物件,也就是 re.Match 物件:
for item in re.finditer(r"\d+", "asd12asxda45asd456sd23"):
print(item)
# <re.Match object; span=(3, 5), match='12'>
# <re.Match object; span=(10, 12), match='45'>
# <re.Match object; span=(15, 18), match='456'>
# <re.Match object; span=(20, 22), match='23'>
6. re.sub
替換函式,將字串裡的指定模式替換成目標字串,然後返回:
re.sub(r"\d+", "***", "asd12asxda45asd456sd23")
# 'asd***asxda***asd***sd***'
7. re.subn
re.subn() 與 re.sub() 函式作用一致,都是替換目標字串,但是返回的是一個元組,分別是替換後的字串和替換的次數:
re.subn(r"\d+", "***", "asd12asxda45asd456sd23")
# ('asd***asxda***asd***sd***', 4)
8. re.compile
re.compile() 函式將正規表示式編譯為一個正規表示式物件,然後可以呼叫前面介紹過的這些正則函式,比如 re.search(),re.match(),re.findall() 等。
pattern = re.compile(r"\d+")
pattern.match("3432asdas334asd34asd")
# <re.Match object; span=(0, 4), match='3432'>
re.complie() 這個函式的操作可以用於永福匹配模式,使程式更加高效。
9. 其他引數
接下來介紹一下正則模組在匹配情況下的一些其他引數。
1) re.I
忽略大小寫。
對於目標字串,如果存在字母大小寫的情況,我們可以對原始字串統一進行大寫或者小寫的操作,然後進行匹配,以忽略大小寫的影響,也可以使用 re.I 引數:
re.search(r"[a-z]+", "123SDAFSDsa234ASDd34".lower())
# <re.Match object; span=(3, 11), match='sdafsdsa'>
re.search(r"[a-z]+", "123SDAFSDsa234ASDd34", re.I)
# <re.Match object; span=(3, 11), match='SDAFSDsa'>
2) re.M
多行匹配模式。
前面介紹 ^
和 $
這兩個符號的匹配的時候介紹過,如果加上 re.M 引數,表示的是可以匹配字串內部有換行符的開頭和結尾的資料:
str_1 = "asd—— _\na你‘'好,\ns9。8?12\v3a\rs,.\ta9\n78d"
target_pattern = r"^a"
print(re.findall(target_pattern, str_1, re.M))
# ['a', 'a']
4、re.Match 匹配物件
對於一些函式,比如前面介紹的 re.search(),返回的就是一個 re.Match 物件,還是以上面的示例為例,如何獲取 re.Match 中的資料呢,下面開始介紹。
1. Match.group()
group() 函式直接呼叫,或者新增引數 0,返回的是匹配的字串資料:
print(result.group())
# asdas9872
print(result.group(0))
# asdas9872
而如果我們匹配的模式使用了小括號 ()
進行了分組,那麼則可以對 group() 函式新增其他 index 表示匹配的分組的結果,比如下面的操作:
str_1 = "..()-+/?asdas9872asdasklqw8267"
target_pattern = r"([a-z]+)(\d+)"
result1 = re.search(target_pattern, str_1)
print(result1.group(0))
# asdas9872
print(result1.group(1))
# asdas
print(result1.group(2))
# 9872
2. Match.__getitem__(g)
re.Match 物件實現了 getitem 這個魔術方法,所以我們可以透過索引的方式對來訪問匹配的結果,其效果和 group(index) 是一致的:
print(result1[0])
print(result1.group(0))
print(result1[2])
print(result1.group(2))
3. Match.groups()
groups() 函式返回的是進行了分組匹配的結果,以元組的形式返回。
比如這裡分組的 result1 物件:
print(result1.groups())
# ('asdas', '9872')
而 result 這個 Match 物件匹配模式裡並沒有使用小括號進行分組,所以返回的結果是空元組:
print(result.groups())
# ()
4. Match.re
返回的是生成這個 Match 物件的正則物件,也就是我們輸入的 target_pattern:
print(result.re)
# re.compile('[a-z]+\\d+')
5. Match.string
返回的是傳遞到生成這個 Match 物件的原始字串:
print(result.string)
# ..()-+/?asdas9872asdasklqw8267
6. Match.start() 和 Match.end()
這兩個函式表示的是匹配上的字串的開始位置和結束位置:
print(result.start(), result.end())
# 8 17
我們對原始字串進行起始位置的擷取就是 Match.group() 的結果:
print(str_1[result.start(): result.end()])
# asdas9872
print(result.group())
# asdas9872
而如果我們前面對匹配模式進行了分組的操作,也就是使用了小括號,比如返回的 result1,我們可以想 start() 和 end() 函式新增索引引數分別表示這兩個分組結果開始和結束的下標位置:
print(result1.groups())
# ('asdas', '9872')
print(str_1[result1.start(1): result1.end(1)])
# asdas
print(str_1[result1.start(2): result1.end(2)])
# 9872
7. Match.span()
返回的是匹配結果的開始和結束位置,以元組的形式返回,其實就是一次性返回 Match.start() 和 Match.end() 的結果。
span() 函式也可以接受分組的引數返回分組的起始位置。
如果想獲取更多後端相關文章,可掃碼關注閱讀: