Python 中的正则表达式

前言

正则表达式很重要,特别是对于字符串的处理。作为一个使用 Python 的人不会使用正则表达式的使用,到底浪费了多少时间在处理字符,天才知道啊,鉴于自己的心酸历程,决定还是好好做做笔记,但还需要多加练习,学以致用。

正则表达式

什么是正则表达式?

正则表达式,即 regular expression,通常简写为 regex 或者 regexp, 简单的来说就是用于查找目标文本中的特定字符串,这些特定字符串遵循正则表达式所描述的规则。因此正则表达式就是用来定义字符串组成的规则的,然后通过函数实现对规则的解释,查找特定的字符串。

应用场景

  • 提取字符串,找出符合规则的字符串,如查找文本中的形如 xxx@xxx.com 的邮箱
  • 替换字符串
  • 网页信息填写时,验证用户名、邮箱等是否符合网站设定的规则

基本语法一览

以下正则表达式,可以查找到诸如 hello-regexregex101_cool110_119等字符串,这些字符串以字母 a-z或数字 0-9、下划线 _、连字符 -,开头和结尾,长度在 3 到 15 之间。图转自 learn_regex.

Regular expression

基础

元字符

正则表达式是用来描述字符串组成的规则的,那么它如何描述呢,就像编程语言有关键字一样,它也有描述规则的特殊字符,称为元字符,这些字符都用特殊的含义。

元字符 描述
. 表示一个任意字符,换行符除外
+ 表示重复 >=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
2
3
4
5
>>> import re
>>> regex = r'(?i)\bcat|dog'
>>> s = 'Here are cAT, cat, dog, Dog'
>>> re.findall(regex, s)
['cAT', 'cat', 'dog', 'Dog']

锚点(定位置)

锚点用于规定匹配的位置,除了 ^$可以分别指定字符串的开头和结尾,还有以下锚点:

锚点 描述
\A 字符串头部
\Z 字符串尾部
\b 单词分界
\B 分单词分界
1
2
3
4
>>> re.search(r'\bcat\b', 'The fat cat sat on the mat.') # 匹配单词 cat
<_sre.SRE_Match object; span=(8, 11), match='cat'>
>>> re.search(r'at\B', 'The faty cat sat on the mat.') # 匹配单词中的 at
<_sre.SRE_Match object; span=(5, 7), match='at'>

零宽度断言(前后预查)

有时候我们需要查找有带有前后缀的文本,但我们不需要前后缀部分,如在如下人员信息文本中提取所有国家名字

1
2
3
4
5
6
7
id:1
Name:Jack
Country:US
id:2
Name:Rose
Country:UK
...

这时候就可以使用 零宽度断言。零宽度断言就是要查找的特定字符的前后缀规则,但这些前后缀只用于约束查找,实际结果不包含这些前后缀,这大概就是为什么称作零宽度(个人理解)。

零宽度断言有四种:

符合 描述
(?=chars) 正先行断言,表示必须有特定后缀 chars
(?!chars) 负先行断言,表示除特定后缀 chars 以外的所有后缀均可
(?<=chars) 正后发断言,表示必须有特定前缀 chars
(?<!chars) 负后发断言,表示除了特定前缀 chars 外的所有前缀均可

从上面的人员信息文本中查找所有国家名可以使用正后发断言,表示国家名前面必须有前缀 Country:

/(?<=(Country:))\w*/gm,这里用到了标志gm 分别表示,在一行内匹配所有符合的,以及在多行匹配

分组

使用正则表达式还可以从字符串中提取子字符串,正则表达式使用 () 成组匹配,我们可以通过定义多个组抽取字符串中的多个子字符串, 如提取邮箱地址的用户名(@之前)和邮箱的域名(@之后),我们可以使用 ^([0-9a-zA-Z]\w*)@(\w*\.com)$,提取出两个 组。

在 Python 中, 使用 _sre.SRE_Match 对象的 group() 方法

1
2
3
4
5
6
7
8
9
>>> email = r'^([0-9a-zA-Z]\w*)@(\w*\.com)$'
>>> pemail = re.compile(email)
>>> m = pemail.match('xman@gmail.com') # 匹配成功就会返回一个 _sre.SRE_Match 对象
>>> m.group(1)
'xman'
>>> m = pemail.match('_xman@gmail.com')
>>> m # 匹配不成功,返回 None
>>> pemail.findall('xman@outlook.com')
[('xman', 'outlook.com')]

其实 零宽度断言 也算是 分组的一种,只不过分组只是用来约束而已。如果不想成组返回但还是需要,成组匹配,如 findall函数,如果存在分组,则会匹配的分组结果,这时可以使用:

(?:)

进行分组匹配,但是结果是没有分组的

1
2
>>> re.findall('^(?:\w+)@(?:\w*\.com)$', 'no@group.com')		     
['no@group.com']

贪婪匹配和惰性匹配

使用正则表达式进行文本匹配时,默认使用贪婪匹配模式,也就是匹配尽可能长的字符串,但是很多时候我们得不到想要的结果,如我们想要在下面文本中查找类似 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 模块进行正则表达式处理,主要有两个步骤:

  1. 将正则表达式,编译成 _sre.SRE_Pattern 对象,
  2. 使用对象的方法使用正则表达式进行匹配、查找、切割等操作。
1
2
3
4
5
6
>>> import re
>>> regex = r'\d{12}' #12个连续数字
>>> p = re.compile(regex) # 编译表达式
>>> s = 'What dose the code 110119120114 mean'
>>> regex.search(regex) # 在字符串中查找
<_sre.SRE_Match object; span=(19, 31), match='110119120114'> # 返回一个 _sre.SRE_Match 对象

由于在 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
2
3
4
5
6
7
8
9
>>> regex = r'\d{11}(?=\W)' # 找 11 位的数字
>>> nums = '12213313451334,566778,13315551666,'
>>> p = re.compile(regex)
>>> p.search(nums)
<_sre.SRE_Match object; span=(3, 14), match='13313451334'>
>>> result.group()
'13313451334'
>>> result.start()
3

标志

正则表达式中的标志 re 模块中的标志
i re.I
m re.M
s,用于使得 .可以表示任意字符,包括换行符 re.S
x,用于使得 正则表达式 可以换行和添加注释 re.X

常用的函数

re.compile(pattern, flags=0)

将一个正则表达式编译成 正则表达式 对象,以便于重复使用,该匹配模式。

1
2
3
4
5
6
# 查找
import re
regex = re.complie(pattern)
result = regex.match(string)
# 等同于
result = re.match(pattern, string)

正则表达式的处理流程:

所有的正则表达式都需要显示或者隐式的编译成 正则表达式 对象,才能进一步使用正则表达式去匹配字符串,因此对于需要重复使用的模式,应该一次性进行编译。

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

matchsearch 区别在于,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
2
3
4
5
6
7
8
9
10
11
>>> import re
>>> regex = r'\w+@\w+\.com'
>>> p = re.compile(regex)
>>> s = 'pig@dog.com,list@tuple.com,len-2@111.com'
>>> res = p.finditer(s)
>>> for r in res:
print(r[0])
#输出
pig@dog.com
list@tuple.com
2@111.com

re.split(pattern, string, maxsplit=0, flags=0) | pattern.split(string[, pos[, endpos]])

根据模式将字符串进行拆分。

1
2
3
4
>>> s = '123,333, 155--134-,were&here,we|--are-you' # 以非数字、非字母不包括 -,以及2个以上的 - 为分隔符
>>> regex = r'\s|-{2,}|[^\w-]'
>>> re.split(regex, s)
['123', '333', '', '155', '134-', 'were', 'here', 'we', '', 'are-you']

参考

  1. learn-regex
  2. 廖雪峰Python教程
  3. Regular expression operations
文章作者: BingSlient
文章链接: https://bingslient.github.io/2019/10/31/Python-正则表达式/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 BingSlient's Blog
打赏
  • 微信
  • 支付寶