正则表达式(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'>
对反斜杠的特殊处理
确实存在匹配单个字面意义上的反斜杠的情况,如匹配如下$LaTeX$:
\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 |
d igital,一位十进制数字符 |
\D |
一位非十进制数字符 |
\w |
w ord,大小写都是可以的 |
\W |
非字母 |
\s |
s pace,空格 |
\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 输入输出编号