前言
正则表达式很重要,特别是对于字符串的处理。作为一个使用 Python 的人不会使用正则表达式的使用,到底浪费了多少时间在处理字符,天才知道啊,鉴于自己的心酸历程,决定还是好好做做笔记,但还需要多加练习,学以致用。
正则表达式
什么是正则表达式?
正则表达式,即 regular expression,通常简写为 regex 或者 regexp, 简单的来说就是用于查找目标文本中的特定字符串,这些特定字符串遵循正则表达式所描述的规则。因此正则表达式就是用来定义字符串组成的规则的,然后通过函数实现对规则的解释,查找特定的字符串。
应用场景
- 提取字符串,找出符合规则的字符串,如查找文本中的形如 xxx@xxx.com 的邮箱
- 替换字符串
- 网页信息填写时,验证用户名、邮箱等是否符合网站设定的规则
基本语法一览
以下正则表达式,可以查找到诸如 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)
1 | import re |
锚点(定位置)
锚点用于规定匹配的位置,除了 ^
、$
可以分别指定字符串的开头和结尾,还有以下锚点:
锚点 | 描述 |
---|---|
\A |
字符串头部 |
\Z |
字符串尾部 |
\b |
单词分界 |
\B |
分单词分界 |
1 | r'\bcat\b', 'The fat cat sat on the mat.') # 匹配单词 cat re.search( |
零宽度断言(前后预查)
有时候我们需要查找有带有前后缀的文本,但我们不需要前后缀部分,如在如下人员信息文本中提取所有国家名字
1 | id:1 |
这时候就可以使用 零宽度断言。零宽度断言就是要查找的特定字符的前后缀规则,但这些前后缀只用于约束查找,实际结果不包含这些前后缀,这大概就是为什么称作零宽度(个人理解)。
零宽度断言有四种:
符合 | 描述 |
---|---|
(?=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()
方法
1 | r'^([0-9a-zA-Z]\w*)@(\w*\.com)$' email = |
其实 零宽度断言 也算是 分组的一种,只不过分组只是用来约束而已。如果不想成组返回但还是需要,成组匹配,如 findall
函数,如果存在分组,则会匹配的分组结果,这时可以使用:
(?:)
进行分组匹配,但是结果是没有分组的
1 | '^(?:\w+)@(?:\w*\.com)$', 'no@group.com') re.findall( |
贪婪匹配和惰性匹配
使用正则表达式进行文本匹配时,默认使用贪婪匹配模式,也就是匹配尽可能长的字符串,但是很多时候我们得不到想要的结果,如我们想要在下面文本中查找类似 xxxrose
的字符串
1 | 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
对象, - 使用对象的方法使用正则表达式进行匹配、查找、切割等操作。
1 | import re |
由于在 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 |
1 | r'\d{11}(?=\W)' # 找 11 位的数字 regex = |
标志
正则表达式中的标志 | re 模块中的标志 |
---|---|
i |
re.I |
m |
re.M |
s ,用于使得 . 可以表示任意字符,包括换行符 |
re.S |
x ,用于使得 正则表达式 可以换行和添加注释 |
re.X |
常用的函数
re.compile
(pattern, flags=0)
将一个正则表达式编译成 正则表达式 对象,以便于重复使用,该匹配模式。
1 | # 查找 |
正则表达式的处理流程:
所有的正则表达式都需要显示或者隐式的编译成 正则表达式 对象,才能进一步使用正则表达式去匹配字符串,因此对于需要重复使用的模式,应该一次性进行编译。
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
默认从头开始匹配。
1
2
3
4 > 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 对象的迭代器。
1 | import re |
re.split
(pattern, string, maxsplit=0, flags=0) | pattern.split
(string[, pos[, endpos]])
根据模式将字符串进行拆分。
1 | '123,333, 155--134-,were&here,we|--are-you' # 以非数字、非字母不包括 -,以及2个以上的 - 为分隔符 s = |