因为正则表达式的在文本处理,字符串匹配中有着重要应用,因此本文对其基本语法规则及在Python中的应用进行了简要介绍。
注:本文大部分是对博文正则表达式30分钟入门教程,LiZeC的正则表达式笔记以及python re库官方文档的归纳和整理,更详细内容可查阅原文。
Examples
正则表达式 | 要匹配的字符串 |
---|---|
\bhi\b |
单词“hi” 不能直接用 hi 来匹配,否则会将him,history,high等单词也匹配上) |
\bhi\b.*\bLucy\b |
hi后面不远处跟着一个Lucy |
0\d{2}-\d{8} |
以0开头,然后是两个数字,然后是一个连字号“-”,最后是8个数字 (也就是中国的电话号码) |
\ba\w*\b |
以字母a开头的单词 |
^[A-Za-z]+$ |
由26个字母组成的字符串 |
^[A-Za-z0-9]+$ |
由26个字母和数字组成的字符串 |
^[0-9]*[1-9][0-9]*$ |
正整数形式的字符串(不能全0) |
(\d{1,3}\.){3}\d{1,3} |
IP地址表达式(应用了分组的概念) |
- 对于上述IP地址匹配,一个缺点是将256.300.888.999这种不可能存在的IP地址也匹配了。如果能使用算术比较的话,或许能简单地解决这个问题,但是正则表达式中并不提供关于数学的任何功能,所以只能使用冗长的分组,选择,字符类来描述一个正确的IP地址:
((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)
0\d{2}-\d{8}|0\d{3}-\d{7}
:这个表达式能匹配两种以连字号分隔的电话号码(应用了分支条件):
一种是三位区号,8位本地号(如010-12345678),一种是4位区号,7位本地号(0376-2233445)|
基本语法
- 正则表达式(regular expression)描述了一种字符串匹配的模式(pattern),主要用来匹配特定的字符串。
- 正则表达式中分为普通字符和特殊字符,普通字符表示匹配和其相同的字符;特殊字符并不匹配他们本身,而是有特殊含义的,具体有:
. ^ $ * + ? { } [ ] \ | ( )
,除了这些特殊字符外都是普通字符。 - 若要匹配这些字符本身,则需要加上
\
进行转义(escape),如\*
表示匹配*
本身。 - 因为python字符串也是使用
\
作为转义字符,因此在python中使用正则表达式需要再加一个反斜杠,即用\\
表示转义;也可以用原声字符串的形式,即在字符串前面加,如r"\w"
。
一些特殊字符含义
字符 | 含义 |
---|---|
. | 匹配任意单个字符(除换行符“\n”外) |
() | 指定分组 |
[] | 指定字符类 |
{} | 指定重复次数 |
\s | 匹配任意的空白符,包括包括空格,制表符(Tab),换行符,中文全角空格等 |
\w | 匹配任意字母、数字、下划线、汉字(即匹配普通字符) |
\s | 匹配任意的空白符(空格、制表符(Tab)、换行符、中文全角空格等) |
\d | 匹配数字(0-9) |
\b | 检测单词的开始或结束(单词边界),如空格,标点符号或者换行 但\b并不匹配这些单词分隔字符中的任何一个,它只匹配一个位置 |
^ | 检测字符串的开始 |
$ | 检测字符串的结束 |
- 一些字符的反义表示
如\w
,\b
这些特殊字符的大写表示相反的含义,具体如下:
字符 | 含义 |
---|---|
\W | 匹配不是字母、数字、下划线、汉字的字符(多用来匹配特殊字符) |
\S | 匹配任意不是空白符的字符 |
\D | 匹配任意非数字的字符 |
\B | 匹配不是单词开头或结束的位置 |
关于\b的一些说明
- 注:
\b, ^, $
匹配的并不是字符,而是一个位置
如对于\blove\b
,用来匹配句子”I love youlove “,其匹配出来的只有第一个love,第二个love不会匹配;其次其只匹配第一个love这个单词本身,而不包括其两端的空格(因为\b只只是检测单词边界,如空格,标点符号或者换行,而并不匹配这些字符)。 - 所以如果想要检测单词边界,同时匹配空白符,则需要再指定
\s
。 - eg.匹配连续出现三次的单词,如果中间不加\s就匹配不出来(\b只匹配位置,不匹配任何字符);同时前后最好用\b来指定,因为这个单词可能在一行的开始(前面无任何字符),或者最后一个单词后面接了\n等情况。
1
repeat_elements = re.findall(r"\b(\w+)\b\s\1\b\s\1\b", "123 mention mention mention 321 123")
重复匹配限定符
以下字符称为“限定符”(限制次数的符号),跟在特殊字符的后面,表示重复该字符特定次数。
如\d+
匹配1个或更多个的数字。
限定符 | 含义 |
---|---|
* | 重复\(\geq\)0次 |
+ | 重复\(\geq\)1次 |
? | 重复0次或1次 |
{n} | 重复n次 |
{n,} | 重复\(\geq\)n次 |
{n,m} | 重复n到m次 |
贪婪、懒惰匹配原则
- 正则表达式的匹配原则为贪婪匹配:即在满足条件的情况下,匹配尽可能多的字符。
eg.a.*b
会匹配以a开始,以b结束的最长的字符串。如果用来搜索aabab
,则会匹配整个字符串aabab
而非aab
。 - 若需要懒惰匹配:即匹配尽可能少的字符,就在相应的重复字符后加
?
即可。
eg.a.*?b
匹配以a开始,以b结束的最短的字符串,搜索aabab
会匹配到aab
和ab
两个子串(之所以没有仅仅匹配到ab
子串,是因为正则表达式的匹配还会考虑开始的先后顺序,最开始匹配的优先级最高)。 - 懒惰匹配限定符如下:
限定符 | 含义 |
---|---|
*? | 重复\(\geq\)0次,但尽可能少重复 |
+? | 重复\(\geq\)1次,但尽可能少重复 |
?? | 重复0次或1次,但尽可能少重复 |
{n,}? | 重复\(\geq\)n次,但尽可能少重复 |
{n,m}? | 重复n到m次,但尽可能少重复 |
字符类
- 在
[
和]
中的若干字符构成一个字符类(character class)。 - 字符类是为了匹配某种字符集合,表示此位置可以匹配这个类中的任意一个字符。
- 整个字符类所起到的作用和普通字符相同,都是只匹配单个字符,因此字符类可作为整体再接受其他限定,如
? + {n,m}
等等。 - 可以使用
-
来表示一个范围,例如[a-c]
表示[abc]
- 在字符类中的特殊符号不被转义
- 反向匹配:在字符类中,如果以^开头,则表示匹配除此字符类中提及的任何其他字符。如
[^5]
匹配任何不是5的字符。
分枝条件
- 在正则表达式表示“或”的逻辑,两个条件用
|
连接即可。
eg.\d{5}-\d{4}|\d{5}
这个表达式用于匹配美国的邮政编码。美国邮编的规则是5位数字,或者用连字号间隔的9位数字。 - 在使用分枝条件时要注意各个条件的顺序,如果上文改成
\d{5}|\d{5}-\d{4}
的话,那么就只会匹配5位的邮编(以及9位邮编的前5位)。因为一旦前面的分枝满足的话就不会再管其他条件了。
注释
不推荐在正则表达式内部写注视,注释推荐写在正则表达式的外部语言中。
正则表达式高级特性
分组
- 上文提到了如何重复单个字符(直接在字符后面加表示重复的限定符即可),但若想重复多个字符,就需要用到分组的概念。
- 用小括号
()
来指定分组(也叫子表达式),之后就可以对这个分组的整体进行重复或其他操作。
eg.(\d{1,3}\.){3}\d{1,3}
,就是一个比较粗糙的IP地址匹配式。
后向引用
- 使用小括号指定一个分组后,这个分组可以作为一个整体在后文中作进一步处理。
- 默认情况下,每个组会有一个组号,从左到右,第一个出现的分组组号为1(注意不是0),第二个为2,以此类推。
- 如何引用:在后问中使用
\
+组号的形式来引用,如\1
表示引用1号分组的。
eg.\b(\w+)\b\s+\1\b
可以来匹配如go go 或kitty kitty 这种连着两个重复单词。
零宽断言
- 用于查找在某些内容前面或后面的东西(但不包括这些内容),类似于
^ $ \b
这种占位符,用于指定一个位置,这个位置应该满足一定的条件(即断言),因此被称为零宽断言。 (?=exp)
:用于匹配exp前面出现的表达式。
e.g\b\w+(?=ing\b)
用于匹配以ing为结尾的单词的前面部分(不包括ing)(?<=exp)
:用于匹配exp后面出现的表达式。
e.g(?<=\bre)\w+\b
用于匹配以re开头的单词的后面部分(不包括re)- 更多示例:
(?<=\s)\d+(?=\s)
:匹配以空白符间隔的数字(不包括这些空白符)((?<=\d)\d{3})+\b
:要给一个很长的数字中每三位间加一个逗号(当然是从右边加起了),你可以这样查找需要在前面和里面添加逗号的部分(用它对1234567890
进行查找时结果是234567890
)
负向零宽断言
- 用于确保某个模式不会出现。与字符类
[^exp]
的区别:虽然不匹配这个字符,但字符类总是会匹配某个字符的,这会限制字符类的应用场景;而负向零宽断言不匹配字符,其只指代一个位置。
eg. 如果用\b\w*q[^u]\w*\b
来匹配“出现了字母q,但是q后面跟的不是字母u”的单词,则像“Iraq, Benq”这种q直接作为最后一个字符的情况就会出错(字符类总要匹配一个字符),因此就需要负向零宽断言。 (?!exp)
:断言此位置的后面不能匹配表达式exp。
eg.\b((?!abc)\w)+\b
匹配不包含连续字符串abc的单词(?<!exp)
:断言此位置的前面不能匹配表达式exp。
eg.(?<![a-z])\d{7}
匹配前面不是小写字母的七位数字。
递归匹配
- 用于匹配一些嵌套的层次结构,如
(100*(50+15))
,如果只是简单地使用\(.+\)
则只会匹配到最左边的左括号和最右边的右括号之间的内容。假如原来的字符串里的左括号和右括号出现的次数不相等,比如(5/(3+2)))
,那我们的匹配结果里两者的个数也不会相等。如果想要想匹配到最长的,而且配对正确的字符串,就需要用到递归匹配。 - 具体内容参见博文正则表达式30分钟入门教程
在Python中使用正则表达式
example code1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20# 查找字符串中连续出现三次的子串(中间以空格分隔)
list_result = re.findall(r"\b(\w+)\b\s\1\b\s\1\b",
"123 mention mention mention 123 entity entity entity")
print(list_result)
# ['mention', 'entity']
match_result = re.finditer(r"\b(\w+)\b\s\1\b\s\1\b",
"123 mention mention mention 123 entity entity entity")
if match_result == None:
print("no repeat elements")
else:
for match in match_result:
print(match.group(), "start:", str(match.start())+",",
"end:", str(match.end()))
# mention mention mention start: 4, end: 27
# entity entity entity start: 32, end: 52
m = re.findall(r"(\w+) (\w+)", "Isaac Newton, physicist")
print(m)
# [('Isaac', 'Newton')],包含多个子分组的情况下会以tuple的形式返回
使用原生字符串
- 正则表达式中使用
\n
表示转义,而python中恰好也使用\n
表示转义,因此在python中使用正则表达式则需要\\
来表示反斜杠。 - 为了节省过多的反斜杠,可以使用Python原生字符串特性,即在字符串开头加上
r
,如r\d
,在这个字符串中每个字符表示其本身,Python不进行转义。
re库
在Python中使用正则表达式直接导入re
库即可 import re
。
常用函数介绍
- 只是简单检测字符串是否存在,使用findall()函数即可;
否则使用finditer(),返回的match对象方便进行之后的操作;
替换、分隔操作直接使用split()、sub()函数;
函数(常用性排序从上至下) | 功能 |
---|---|
re.findall() | 在字符串中查找所有满足条件的子串,返回string list |
re.finditer() | 在字符串中查找所有满足条件的子串,返回匹配出的macth对象的iterator |
re.split() | 将正则表达式作为separator来分割字符串,返回string list |
re.sub() | 在字符串中替换所有匹配正则表达式的子串,返回替换后的字符串 |
re.search() | 在字符串中查找匹配的子串第一次出现的位置,匹配成功返回match对象,否则返回None |
re.match() | 从字符串的起始位置开始匹配,即匹配字符串的开头是否和正则表达式匹配,返回match对象或None |
re.fullmatch() | 匹配整个字符串是否和正则表达式匹配,返回match对象或None |
re.escape() | 自动在字符串中为特殊字符添加 “\” 转义符,返回修改后的字符串 具体见下文解释 |
参数说明:
- re.findall(pattern, string, flags=0)
pattern: 表示正则表达式的字符串,推荐使用python原生字符串r"string"
flags: 正则表达式的控制标记
return: 返回string list,list中每一项表示匹配出来的字符串 - re.sub(pattern, repl, string, count=0, flags=0)
repl: 用来替代匹配到的子串的字符串 - re.search(pattern, string, flags=0)
参数同上
return: 匹配成功返回match对象,否则返回None - re.match(pattern, string, flags=0)
参数同上
return:匹配成功返回match对象,否则返回None - re.fullmatch(pattern, string, flags=0)
参数同上
return:匹配成功返回match对象,否则返回Non - re.escape(pattern)
eg.pattern1 = re.escape('python.exe'))
,pattern1就为”python\.exe”,可直接作为pattern参数传入其他函数中,用来匹配“python.exe”。当pattern含有大量特殊字符时使用这个函数就很方便。
Match类
re.search()和re.match()两个函数的返回对象,包含了匹配的相关信息,常用函数和属性如下:
名称 | 说明 |
---|---|
match.group() | 返回特定的分组,string或tuple的形式 |
match.start() | 返回特定分组的起始地址 |
match.end() | 返回特定分组的结束地址 (会比实际大1位,因为python字符串截取前闭后开的特性) |
match.string | 为传入到re.search()或re.match()的待匹配字符串 |
match.groups() | 以tuple的形式返回所有分组,element即为各个分组 |
- match.group([group1(int), …])
[group1](可选):需要返回的组号;没有传参时组号默认为0,对应整个匹配到的字符串,通常情况不用传参数即可。
这里的分组指的就是在正则表达式中通过小括号()
指定的分组,每个分组从左到右会有一个组号,且组号从1开始。
return: string字符串 - match.start([group])、match.end([group])
两个函数中group都是指组号,返回特定组号的起始、终止地址,默认即指整个匹配的字符串,见小节开头的例子; - 分组的对应子串可通过
match.string[m.start(g):m.end(g)]
来获取。
控制标记flag介绍
用于规定正则表达式的一些匹配规则,作为flags
参数传入正则表达式函数
|标记名(简写/全称)|含义|
|:-:|:-|
|re.A / re.ASCII|使\w,\b,\s和\d只匹配ASCII字符
eg. 不会匹配汉字和其他Unicode字符|
|re.I / re.IGNORECASE|忽略正则表达式大小写|
|re.M / re.MULTILINE|使得^表示每一行的开始,$表示每一行的结束
原义仅表示一个单词的开始和结束|
|re.S / re.DOTALL|使得.可以匹配\n字符|
|re.X / re.VERBOSE|忽略正则表达式内部的空白符|
注:这些变量在VSCode的python下没有提示,但可以运行。
str.replace()和re.sub的比较
- str.replace(old, new[, count])是字符串的替代函数,其中new为替换的字符串,old为待替换的字符串,old只能为substring而不能为字符串,因此替换功能较为简单。
- re.sub(pattern, repl, string, count=0, flags=0)则使用了正则表达式,可进行更复杂的替换操作,但同时开销也更大。
- 因此能用replace()尽量用,复杂的替换操作再使用正则表达式。
Post Date: 2019-02-07
版权声明: 本文为原创文章,转载请注明出处