正規表示式(regular expression),用於字串匹配、替換,是字串的重要處理方式。其中正則是有規則,有規律的意思。正規表示式有三要素,本文主要介紹 Python 中的這三要素:
- 要處理的字元(串),需要注意轉義序列
- 用於處理的程式,即正則引擎:Python 自帶
re模組 - 處理規則:正規表示式語法
1 Python 字串
1.1 轉義序列
Python 字串可以包含兩部分:
- 普通字元組成的字串
- 轉義序列組成的字串
轉義序列(Escape Squence)由跳脫字元(Escape Character)和後續字元組成,跳脫字元放在字元序列前面時,它將對它後續的幾個字元進行替代並解釋。跳脫字元是元字元(Meta Character)的一種特殊情況。Python 中跳脫字元是反斜槓(\)。Python 中所有轉義序列如下表:
| 轉義序列 | 意義 |
|---|---|
\ (行尾) | 續行符 |
\a | alert |
\b | backspace |
\n | new line |
\r | carriage return |
\t | table |
\v | vertical table |
\' | single qoute |
\" | double qoute |
\\ | back slash |
\xyy | hex |
\0zz | oct, start with number 0 |
\000 | null |
通常也把轉移序列成為跳脫字元。跳脫字元擁有兩個含義:
- 轉義序列標誌,如 Python 中的反斜槓
- 轉義序列,由轉義序列標誌符和後續字元組成,整體的有特殊意義
後面的描述中,跳脫字元特指轉義序列。
處理引號也可以用跳脫字元:Python 並不明確區分單雙引號,只要要匹配使用就行;如要表示一個字面意義上的引號,需用另外一種引號來巢狀,或者跳脫字元:
"'" # use another kind of qoute
"\"" # use escape character
輸入這些符號時需要多次敲鍵盤,但 Python 都把它們當成長度為1的字元來處理:
In [1]: len("\017")
Out[1]: 1
In [2]: len("\n")
Out[2]: 1
In [3]: len("\\")
Out[3]: 1
1.2 Python 中轉義序列標誌符
反斜槓是 Python 中轉義序列標誌符,它有如下特性需要注意:
- 表示一個字面意義的
\需要轉義序列標誌符\ - Python 中顯示一個字面意義上
\則是\\
In [1]: len("")
Out[1]: 0
In [2]: len("\")
File "<ipython-input-2-553b6950c0a1>", line 1
len("\")
^
SyntaxError: EOL while scanning string literal
In [3]: len("\\")
Out[3]: 1
In [4]: "\\"[0]
Out[4]: "\\"
轉義序列標誌只有在特定的字元組合前才有效果,如果後面的字元組合不能組合為轉義,則只會被當成簡單的字元。
比如\n是特殊含義,而\m沒有,\m只會被看成是一個字面上的\和m組成的長度為 2 的字串。
1.3 原始字串
在某些特定情況下希望字元是所見即所得。一個\和n組合在一起就是表示兩個字元,而不是換行符。此時可以用 Python 的原始字串(Raw String),在要處理的字元前面加上r:
In [1]: len(r"\n")
Out[1]: 2
In [2]: len(r"\")
File "<ipython-input-6-aa225c032aa6>", line 1
len(r"\")
^
SyntaxError: EOL while scanning string literal
In [3]: len(r"\\")
Out[3]: 2
原始字串並非萬能:單獨的反斜槓\沒法用原始字串表示,2 中把最後的引號給轉義掉,3 是一個原始字串,但表示兩個反斜槓。無法用原始字串表是轉義序列。
2 Python 正則引擎
用於匹配的程式叫正規表示式引擎,Python 自帶re的模組。當然還有些第三方庫正則引擎,但並不是主要介紹的物件。需要提前匯入re模組:
In [1]: import re
同時,關於匹配的詳細細節也不是本文介紹的重點。本文主要介紹正則引擎常用函式的使用,常見函式有兩種用法:
re.func(REG, string, flag) # call the func with re directly
re.compile(REG, flag).func(string) # call the compiled object
通常認為先 compile(compile是re模組的函式),後運算可以提升效率。但實際上並不特別關注效率問題:
- 某些高效的正規表示式可能難以理解
- Python 本身執行就想對慢一些
- 本文主要介紹使用,並不過分關注原理
函式參數意義如下:
REG(regular expression)就是正規表示式,第三部分會詳細介紹。string是要匹配的字串,Python 字串。flag是正則運算時候的參數,會在介紹具體函式時候介紹。
常見的func是:
match匹配字串的開始,返回一個re.Match(匹配上)或None(沒匹配上)search找到一個匹配就返回,即使有多個也只返回第一個,返回一個re.Match(匹配上)或者None(沒匹配上)findall找到所有匹配,返回一個列表(第四部分詳細介紹這個列表)finditer找到所有匹配,返回一個迭代器split對字串按照一定規則切分,返回一個列表sub對字串進行替換,返回替換後的字串subn對字串進行替換,返回 tuple,前面是替換後的字元,後面是總共替換次數
所謂re.Match,有如下方法:
re.match.span() # span, tuple
re.match.start() # start index
re.match.end() # end index
re.match.group() # match group
re.match.group_dict() # match group
其中group表示匹配的組, 會在後面捕獲組一節中詳細介紹。這裡可以通過一個例子就re.Match進行簡單說明。由於使用例子要用到正規表示式,這裡先給出正規表示式的一條規則:大部分字元都匹配自身。
In [1]:x = re.search("abc", 'zyxabcdre')
In [2]: x
Out[2]: <re.Match object; span=(3, 6), match='abc'>
In [3]: x.start()
Out[3]: 3
In [4]: x.end()
Out[4]: 6
In [5]: x.span()
Out[5]: (3, 6)
In [6]: x.group()
Out[6]: 'abc'
In [7]: x.group(0)
Out[7]: 'abc'
In [8]: x.groupdict()
Out[8]: {}
In [9]: re.match("abc", 'zyxabcdre')
match匹配的是開始,如果開始沒有匹配到,就返回None;而search可以匹配字串的中間部分,匹配到一個就返回。
flag可以給匹配新增更多選項,常見的flag如下:
flag | 作用 |
|---|---|
re.IGNORECASE | 忽略大小寫 |
re.DOTALL | 點可以匹配換行符 |
re.MULTILINE | 多行模式 |
3 正規表示式語法
這也是通常說的正規表示式,正規表示式用 Python 字串實現,它們屬於 Python 字串,正則引擎處理正規表示式時候有額外的語法。
從第二部分知道,大部分字元都匹配它們自己,少數字符可以匹配其它字串,它們需要被重點關注:
.
[]
^
$
-
*
+
?
{}
\
()
|
3.1 匹配單個字元
3.1.1 大部分字元匹配自己
匹配大小寫敏感,可新增re.IGNORECASE通配大小寫
In [1]: re.search('a', 'ABCD')
In [2]: re.search('a', 'ABCD', re.IGNORECASE)
Out[2]: <re.Match object; span=(0, 1), match='A'>
轉義序列也包含其中,但不包含反斜槓
In [1]: re.search('\n', 'ab\ncd')
Out[1]: <re.Match object; span=(2, 3), match='\n'>
對反斜槓的特殊處理
確實存在匹配單個字面意義上的反斜槓的情況,如匹配如下:
\section{back slash}
若按普通轉義序列一樣處理反斜槓,即用\\作為正規表示式,則會報錯:
In [1]: re.search("\\", "\\x")
---------------------------------------------------------------------------
error Traceback (most recent call last)
<ipython-input-2-ace9affb99aa> in <module>
----> 1 re.search("\\", "\\x")
/usr/local/lib/python3.10/re.py in search(pattern, string, flags)
199 """Scan through string looking for a match to the pattern, returning
200 a Match object, or None if no match was found."""
--> 201 return _compile(pattern, flags).search(string)
202
203 def sub(pattern, repl, string, count=0, flags=0):
/usr/local/lib/python3.10/re.py in _compile(pattern, flags)
302 if not sre_compile.isstring(pattern):
303 raise TypeError("first argument must be string or compiled pattern")
--> 304 p = sre_compile.compile(pattern, flags)
305 if not (flags & DEBUG):
306 if len(_cache) >= _MAXCACHE:
/usr/local/lib/python3.10/sre_compile.py in compile(p, flags)
762 if isstring(p):
763 pattern = p
--> 764 p = sre_parse.parse(p, flags)
765 else:
766 pattern = None
/usr/local/lib/python3.10/sre_parse.py in parse(str, flags, state)
940 # parse 're' pattern into list of (opcode, argument) tuples
941
--> 942 source = Tokenizer(str)
943
944 if state is None:
/usr/local/lib/python3.10/sre_parse.py in __init__(self, string)
230 self.index = 0
231 self.next = None
--> 232 self.__next()
233 def __next(self):
234 index = self.index
/usr/local/lib/python3.10/sre_parse.py in __next(self)
243 char += self.decoded_string[index]
244 except IndexError:
--> 245 raise error("bad escape (end of pattern)",
246 self.string, len(self.string) - 1) from None
247 self.index = index + 1
error: bad escape (end of pattern) at position 0
原因如下:正則引擎首先對正規表示式處理,把\\(兩個連續反斜槓)翻譯為\(一個反斜槓),此時正規表示式變成\"(一個反斜槓結合後面的引號),正規表示式缺少字串結束標誌符—引號",報錯。(正則引擎對正規表示式還會有其它處理,後面會介紹。)正確姿勢是用四個反斜槓:
In [1]: re.search("\\\\", "\\subgraph")
Out[1]: <re.Match object; span=(0, 1), match='\\'>
好傢伙!為匹配一個字面意義上的反斜槓,需要在正規表示式中用四個連續反斜槓。這也是所謂的反斜槓災難。為了避免複雜的寫法,正規表示式中也有原始字串(Raw String),作用是:正則引擎處理正規表示式時候,不把連續兩個反斜槓\\翻譯為一個反斜槓\。
In [1]: re.search(r"\\", "\\x")
Out[1]: <re.Match object; span=(0, 1), match='\\'>
為了讓正規表示式和 Python 字串更加接近,一條推薦的規則是:不管多麼簡單的規則,都採用原始字串的方式進行匹配。
3.1.2 點.匹配所有字元
點.匹配所有字元,這個和萬用字元中星號*表示所有字元一樣。通常點並不能匹配換行符,加入re.DOTALL就可以了。
需要注意的是這裡說的是單個字元。
3.1.3 []表示集合
中括號([])表示範圍,匹配中括號中任意一個元素。中間是或的關係,元素之間不需要隔開。
In [1]: re.search(r'[1234]','843')
Out[1]: <re.Match object; span=(1, 2), match='4'>
中括號中的元素可以重複,但是並沒有特殊意義:
In [1]: re.search(r'[ddxs]','d')
Out[1]: <re.Match object; span=(0, 1), match='d'>
中括號中大部分字元(包含轉移序列)都只表示原本意思,除了特殊字元:點(.)可以匹配任意字元,但在中括號([])中卻只匹配字面上的點(.):
In [1]: re.search(r'.', ',')
Out[1]: <re.Match object; span=(0, 1), match=','>
In [2]: re.search(r'[.]', ',')
In [3]: re.search(r'[\n]', 'x\n')
Out[3]: <re.Match object; span=(1, 2), match='\n'>
對某些需要轉義表示的字元,也可以放到中括號中來避免使用轉義符號,比如反斜槓:
In [1]: re.search(r"[\\]", '\\x')
Out[1]: <re.Match object; span=(0, 1), match='\\'>
需要特殊關注的字元是 ^、 -、 [,規則如下:
^符號表示取反,但是^一定要新增在最開始
In [1]: re.search(r'[^1234]','1843')
Out[1]: <re.Match object; span=(1, 2), match='8'>
如果^在中間,則只會被當成是一個普通字元
In [1]: re.search(r'[7^1234]','1843')
Out[1]: <re.Match object; span=(2, 3), match='1'>
- 中括號中使用
-表示範圍,實現對錶達式的精簡
r"[0-9]" # equal r"[0123456789]" in regular expression
r"[a-z]" # equal r"[abcdefghijklmnopqrstuvwxyz]"in regular expression
需注意若-前後不是範圍,則只會匹配普通的-符號:
In [1]: re.search("[-a]", "b-")
Out[1]: <re.Match object; span=(1, 2), match='-'>
- 中括號開始標誌
[如果非要表示字面意義上左中括號([),則要使用\[:
In [1]: re.search(r"[]]", ']')
Out[1]: <re.Match object; span=(0, 1), match=']'>
In [2]: re.search(r"[[]", ']')
<ipython-input-44-863737df3ef5>:1: FutureWarning: Possible nested set at position 1
re.search(r"[[]", ']')
當要處理(字面意義)中括號時候,最好通通用轉義序列。
3.1.4 正規表示式轉義序列
它們雖然是反斜槓(\)和某個字元的組合,但不是Python 轉義序列:只有正則引擎會對它們特殊處理,而在其它情況下只會被看成是普通字串。它們也可看成對中括號([])的擴充。如下正規表示式含義比較清楚:
r1 = "[0-9]"
r2 = "[a-zA-Z_]"
r1表示一位十進位制數,r2表示任意字母數字或下劃線。可以用正規表示式轉義序列進一步簡寫:
r1 = "\d"
r2 = "\w"
更多的正規表示式轉義序列如下:
| 簡寫方式 | 意義 |
|---|---|
\d | digital,一位十進位制數字符 |
\D | 一位非十進位制數字符 |
\w | word,大小寫都是可以的 |
\W | 非字母 |
\s | space,空格 |
\S | 非空格 |
- 一個規律是大寫為小寫取反
正規表示式中的轉義序列並不會被 Python 字元特殊對待。此時反斜槓只會被看成字面意義上的反斜槓。
In [1]: len("\d")
Out[1]: 2
前面介紹正規表示式原始字串時候提到,正則引擎會對首先正規表示式分析,所謂的正規表示式跳脫字元就是被正則引擎進行處理。
3.2 字串匹配
實際中更有用的是字串的匹配。如同字串對字元的擴充方式,正規表示式並列排布,則匹配並列排布的字元和字串:
- 若正規表示式
regA、regB分別匹配字元chaA、chaB,則正規表示式regAregB匹配字串chaAchaB - 若正規表示式
regA、regB分別匹配字串strA、strB,則正規表示式regAregB匹配字串strAstrB
reg1 = r"\d"
reg2 = r"\d\d"
reg3 = r"\d\w"
re.search(reg1, "56") # 5
re.search(reg2, "56") # 55
re.search(reg3, "5x") # 5x
3.2.1 小括號界定正規表示式範圍
並列排布的運算並不都是從左往右計算,而是依據運算的優先順序從高到低進行運算。為了說明優先順序會影響匹配的結果,這裡引入一個優先順序極低的運算子號|,它表示或的關係。
In [1]: reg1, reg2 = r'a', r'b|c'
In [2]: re.match(r'ab|c', 'ac')
In [3]: re.match(r'ab|c', 'c')
Out[3]: <re.Match object; span=(0, 1), match='c'>
In [4]: re.match(r'ab|c', 'ab')
Out[4]: <re.Match object; span=(0, 2), match='ab'>
直接把reg1和reg2組合到一起,並不能匹配ac,只能匹配ab或者c了。實際上匹配的優先順序被修改。字元組合優先順序高於或(|)運算。此時如果要實現匹配ac,可以新增小括號(實現:
In [1]: re.match(r'a(b|c)', 'ac')
Out[1]: <re.Match object; span=(0, 2), match='ac'>
小括號界定正規表示式的範圍,改變了匹配優先順序。新增非字面意思的小括號並不是匹配小括號的意思。
In [1]: re.search(r'x|y|z', "z")
Out[1]: <re.Match object; span=(0, 1), match='z'>
In [2]: re.search(r'(x)|(y)|(z)', "z")
Out[2]: <re.Match object; span=(0, 1), match='z'>
同時,小括號可以把一個正規表示式界定為一個組(group)。後續可以對這個組進行操作。下一節中將對組進行說明。
3.2.2 重複匹配
根據前面的規則,下面的正規表示式用於匹配多位十進位制數:
\d
\d\d
\d\d\d
\d\d\d\d
如果要匹配位數更多的數,則需要加長正規表示式。顯然這樣並不方便,於是就有了如下簡寫:
(REG){m,n} # 至少m次,至多n次
(REG){,n} # 至少0次,至多n次
(REG){m,} # 至少m次
(REG){m} # 只能是m次
特殊情況下可繼續簡寫:
(REG){0,1} -> (REG)?
(REG){1,} -> (REG)+
(REG){0,} -> (REG)*
上面檢索都是貪婪的:會盡可能長地匹配。在正規表示式後面加上?實現非貪婪搜尋:
In [1]: re.search(r'm{2,3}', "mmm")
Out[1]: <re.Match object; span=(0, 3), match='mmm'>
In [2]: re.search(r'm{2,3}?', "mmm")
Out[2]: <re.Match object; span=(0, 2), match='mm'>
除了對單個字元這樣操作,也可以把用括號,把多個正規表示式作為一組進行操作:
In [1]: re.search(r'(ab)+', 'abababxxx')
Out[1]: <re.Match object; span=(0, 6), match='ababab'>
In [2]: re.search(r'ab+', 'abababxxx')
Out[2]: <re.Match object; span=(0, 2), match='ab'>
1 把ab看成一個整體,進行重複匹配;2 是匹配a和多個b。這裡括號讓一系列正規表示式組成一個組,然後對整個組進行操作。
3.3 捕獲組、非捕獲組與命名組
3.3.1 小括號與捕獲組
總結一下小括號作用:
- 界定正規表示式範圍
- 改變匹配的優先順序
- 把一系列正規表示式匹配的結果對應到組(group)中
REG是一個合法的正規表示式:
(REG)
此時REG匹配到的結果就會存到一個 group 中,可以通過group函式進行訪問。
In [1]: x = re.search(r'((\d)\w(\d))', "a1b3x")
In [2]: x.group(0)
Out[2]: '1b3'
In [3]: x.group(1)
Out[3]: '1b3'
In [4]: x.group(2)
Out[5]: '1'
In [5]: x.group(3)
Out[5]: '3'
In [6]: x.group(4)
---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
<ipython-input-29-b8b09168b4e1> in <module>
----> 1 x.group(4)
IndexError: no such group
In [7]: x.group(1, 3)
Out[7]: ('1b3', '3')
以正規表示式最左邊為1,從左往右開始計數,(非轉義)左括號(()排列的順序就是 group 函式訪問時候的參數,此時返回當前左括號和對應右括號中的正規表示式匹配的結果。group()和group(0)等價,都表示整個正則表達是匹配的結果。
In [1]: x = re.search(r'\d', '5')
In [2]: x.group(0)
Out[2]: '5'
In [3]: x.group()
Out[3]: '5'
這種用小括號包圍正規表示式得到的組就是捕獲組(Capture Group)。捕獲組的結果會儲存到記憶體中,可以通過 group 的 index 進行訪問。
3.3.2 非捕獲組
對於組而言,儲存匹配結果並且訪問並非一定必要:有時僅僅想知道有這麼個匹配(並不想知道具體匹配結果),或者說儲存結果這個過程佔用空間、影響速度而變得不可以。此時就引入非捕獲組(Non Capture Group),只表示匹配關係,不儲存具體結果:
(?:REG)
非捕獲組也使用了小括號()來界定範圍,單非捕獲組正規表示式前面會有問號(?)和冒號(:)。
問號(?)來自 Perl(大部分語言的正規表示式實現都受 Perl 影響)。Perl 從 4 升級到 5 時,為了保持相容性引入了(?的寫法:對一個非轉義的左括號(()用問號(?)進行重複匹配沒有意義,換言之(?並不會引起歧義;
從另一個角度來看,不會有一個合法的正規表示式以問號(?)開頭。
冒號(:)表示普通非捕獲組,表示匹配當前位置的字元(串)。當然,還有其它非捕獲組,如後向斷言(lookbehind)和前向斷言(lookahead)。
非捕獲組不會計入捕獲組的 index 中,無論是否同時存在捕獲組:
In [1]: x = re.search(r'((?:\d)\w(\d))', "a1b3x")
In [2]: x.group(0,1,2)
Out[2]: ('1b3', '1b3', '3')
In [3]: x.group(3)
---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
<ipython-input-9-6cd92edb32bb> in <module>
----> 1 x.group(3)
IndexError: no such group
group(2)匹配最後一個3,沒有group(3)。非捕獲組不計入 group 結果只針對非捕獲組所在的括號,但對非捕獲組外面或者內部的捕獲組並沒有影響:
In [1]: x = re.search(r'([a-c])(?:[d-f](<[g-h]>))','adg')
In [2]: x.group(0,1,2)
Out[2]: ('adg', 'a', 'g')
3.3.3 命名組
有時除了用 index 來訪問匹配內容,還希望賦予匹配一個有意義的名字。此時可以用命名組(Named Group),它屬於捕獲組,主要解決組命名的問題。
(?P<name>REG)
P表示這是Python 正規表示式的語法(而非來自 Perl)。命名組是一種捕獲組,之前捕獲組使用 index 來訪問 group 的方式依舊有效。命名組在此之外新增了用名字訪問的方式,同時會獲得一個非空的groupdict:
In [1]: x = re.search(r'(?P<hh>[a-c])(?:[d-f](<[g-h]>))','adg')
In [2]: x
Out[2]: <re.Match object; span=(0, 3), match='adg'>
In [3]: x.group(0,1,2)
Out[3]: ('adg', 'a', 'g')
In [4]: x.group('hh')
Out[4]: 'a'
In [5]: x.groupdict()
Out[5]: {'hh': 'a'}
3.3.4 組的應用—匹配重複的字元(串)
如果要匹配連續重複的字串,則可以用\{num},其中{num}是數字,一定要是一個存在的 index,且不能是0,這也要求要重複的物件一定要在小括號中。
In [1]: re.search(r'(\d)\1{2}', '21112121211')
Out[1]: <re.Match object; span=(1, 4), match='111'>
上面的\1中的1就是 group 的 index。
In [1]: re.search(r'(\d)\0{2}', '21112121211')
In [2]: re.search(r'\d\0{2}', '21112121211')
In [3]: re.search(r'(\d)\2{2}', '21112121211')
---------------------------------------------------------------------------
error Traceback (most recent call last)
<ipython-input-32-86e83c2fdb65> in <module>
----> 1 re.search(r'(\d)\2{2}', '21112121211')
/usr/lib64/python3.9/re.py in search(pattern, string, flags)
199 """Scan through string looking for a match to the pattern, returning
200 a Match object, or None if no match was found."""
--> 201 return _compile(pattern, flags).search(string)
202
203 def sub(pattern, repl, string, count=0, flags=0):
/usr/lib64/python3.9/re.py in _compile(pattern, flags)
302 if not sre_compile.isstring(pattern):
303 raise TypeError("first argument must be string or compiled pattern")
--> 304 p = sre_compile.compile(pattern, flags)
305 if not (flags & DEBUG):
306 if len(_cache) >= _MAXCACHE:
/usr/lib64/python3.9/sre_compile.py in compile(p, flags)
762 if isstring(p):
763 pattern = p
--> 764 p = sre_parse.parse(p, flags)
765 else:
766 pattern = None
/usr/lib64/python3.9/sre_parse.py in parse(str, flags, state)
946
947 try:
--> 948 p = _parse_sub(source, state, flags & SRE_FLAG_VERBOSE, 0)
949 except Verbose:
950 # the VERBOSE flag was switched on inside the pattern. to be
/usr/lib64/python3.9/sre_parse.py in _parse_sub(source, state, verbose, nested)
441 start = source.tell()
442 while True:
--> 443 itemsappend(_parse(source, state, verbose, nested + 1,
444 not nested and not items))
445 if not sourcematch("|"):
/usr/lib64/python3.9/sre_parse.py in _parse(source, state, verbose, nested, first)
523
524 if this[0] == "\\":
--> 525 code = _escape(source, this, state)
526 subpatternappend(code)
527
/usr/lib64/python3.9/sre_parse.py in _escape(source, escape, state)
421 state.checklookbehindgroup(group, source)
422 return GROUPREF, group
--> 423 raise source.error("invalid group reference %d" % group, len(escape) - 1)
424 if len(escape) == 2:
425 if c in ASCIILETTERS:
error: invalid group reference 2 at position 5
3.4 關係匹配
有時候希望匹配的字串前面或者後面滿足一些條件,但是對條件本身的內容並不關心。此時就要用到關係匹配。
關係匹配也可以認為是零寬度匹配,返回寬度為零的字串。
3.4.1 開頭結尾匹配
^和$分別匹配開頭和結尾:
In [1]: re.search(r"^c\w", 'cacb')
Out[1]: <re.Match object; span=(0, 2), match='ca'>
In [2]: re.search(r"\w$", 'cacb')
Out[2]: <re.Match object; span=(3, 4), match='b'>
In [3]: re.search(r"https|http","https")
Out[3]: <re.Match object; span=(0, 4), match='http'>
In [4]: re.search(r"(http)|(https)", "https")
Out[4]: <re.Match object; span=(0, 4), match='http'>
更寬泛的條件是邊界匹配,可用\b來表示正規表示式。
3.4.2 後向斷言(lookbehind)
實際上要求字串前面滿足一些條件。 這翻譯容易讓人迷惑,估計這裡是說要著重觀察後面的結果並且返回。
(:<=REG) # positive
(:<!REG) # negative
In [1]: re.search(r'(?<=\d)abc', 'abc1abcdabc\n')
Out[1]: <re.Match object; span=(4, 7), match='abc'>
In [2]: re.search(r'(?<!\d)abc', 'abc1abcdabc\n')
Out[2]: <re.Match object; span=(0, 3), match='abc'>
In [3]: re.search(r'(?<=c)(?<=\d)abc', 'abc1abcdabc\n')
1 要求前面一定要有一個 \d;而 2 正好相反,只返回 4~7 處的abc。
後向斷言是一種零寬度匹配,它匹配長度為零的字元(串)。按照前面正規表示式的排列規則,多個正規表示式並列排布表示一起匹配。由於零寬度字元相加還是零款度字元,多個後向斷言並列排布,還是匹配一個結果。此時就起到了一個與運算的效果。
In [1]: re.search(r'(?<=c\d)abc', 'abc1abcdabc\n')
Out[1]: <re.Match object; span=(4, 7), match='abc'>
3.4.2 前向斷言(lookahead)
後向斷言對稱操作,表示式後面要滿足一定條件。
(?=REG) # positive
(?!REG) # negative
這兩種斷言也可以聯合使用:
In [1]: re.search(r'(?<!\d)abc(?=\d)', 'abc1abcdabc\n')
Out[1]: <re.Match object; span=(0, 3), match='abc'>
3.4.3 用斷言來拼湊正規表示式取反
如何用正規表示式來確認一個字串中沒有另外的字串?
3.5 正規表示式優先順序
如同程式語言的運算都有運算優先順序一樣,正規表示式也有優先順序,當正規表示式遇到一起時,就需要考慮其優先順序。 下表列出了優先順序,從上到下遞減,從左到右遞減:
\跳脫字元(),(?:),(?=),(?!),[]\*,+,?,{n},{n,},{n,m}^,$,\任何元字符、任何字元 定位點和序列(即:位置和順序)|“或”操作字元
4 其它函式
4.1 re.findall找到所有匹配
re.findall返回所有匹配結果組成的列表:
In [1]: re.findall(r'[a-z][A-Z]', 'aAxxxxxxbBxxxxxx')
Out[1]: ['aA', 'bB']
In [2]: re.findall(r'([a-z])([A-Z])', 'aAxxxxxxbBxxxxxx')
Out[2]: [('a', 'A'), ('b', 'B')]
若正則表達中沒有捕獲組,返回列標的元素是整個匹配(group()或group(0)),如上面的 1;如果有捕獲組,則列表元素是所有 group 組成的 tuple,如上面的 2。
4.2 re.split分割字串
re.split用於對字串進行切片。比 Python 自帶的str.split功能更加強大。
re.split(reg, string, maxsplit=0)
maxsplit的意思是最多切分次數,預設值0表示所有都切分。
In [1]: re.split(r"\d", "20python21")
Out[1]: ['', '', 'python', '', '']
In [2]: re.split(r"\d", "20python21", 1)
Out[2]: ['', '0python21']
split的結果包含空字串。
4.3 re.sub替換字串
re.sub 用於匹配物件替換,sub是 substring 的意思。
re.sub(reg, repl, string)
其中repl是替換方式,可以是固定字串、函式或者lambda表示式,對於後面兩種情況,操作的物件是match group:
In [1]: re.sub(r'\d', 'NUM', "hello world 5")
Out[1]: 'hello world NUM'
In [1]: re.sub(r'\d', lambda x: str(int(x.group()) + 3), "hello world 5")
Out[1]: 'hello world 8'
字串替換函式replace替換物件只能是固定的字串,而正規表示式擴充了替換的物件。多次呼叫replace的結果並不一定和re.sub等價。
和sub相似的函式是subn,返回一個 tuple,兩個元素依次是替換後的字串和替換次數:
In [1]: re.subn(r'\d', lambda x: str(int(x.group()) + 3), "hello world 5")
Out[1]: ('hello world 8', 1)
5 正規表示式實戰
- 自動給 markdown 標題編號
- 自動給程式碼中的 IPython 輸入輸出編號