正则表达式


介绍

在 Python 中,正则表达式(regular expression)是一种用于处理或者匹配字符串的强大工具,这个工具允许你根据需求来编写匹配字符串的规则,用这个 “规则” 去验证字符串是否符合我们想要的特征。

正则表达式在字符串处理方面的功能相当强大,应用场景包括数据清洗、表单验证、日志分析、文本处理和搜索和替换等,包括现在火热的 Chatpgt 人工智能、nlp 自然语言处理都离不开正则表达式作为基础。

除了上面提到的介绍外,正则表达式还有以下的特点:

特点 描述
灵活性 正则表达式提供了丰富的语法规则,可以灵活地匹配各种各种字符、重复次数、位置和范围,从而能够精确地满足你的各种需求。
通用性 正则表达式在多种编程语言和文本编辑器中都得到支持,因此可以在不同项目中重复使用你的正则表达式知识。
高效性 正则表达式引擎经过优化,可以在大量文本中快速进行匹配和搜索。当正确使用时,正则表达式可以高效地在文本中定位所需的信息。

对于想接触自然语言处理的朋友们,可以在这一章节认真学习,正则表达式的入门相对难度较高,但是在熟练掌握后不论你是开发者、数据分析师,还是文本编辑爱好者,都会让你在所属的领域更具竞争力。

笔者很多年前在学习正则表达式时,翻阅了大量的资料,发现很少有教程能够真正地把正则表达式的核心学习思路清晰地讲明白,因此学习的过程磕磕绊绊,所以笔者希望能站在初学者的角度来编写这份教程,帮助更多的人掌握这个强大的工具。


入门

正则表达式是 Python 中的一个内置模块,名称叫 re ,在使用前需要将模块导入。

import re

在详细讲解这个模块之前,先通过一个例子让大家对正则表达式有一个简单的概念:我希望找到一句话中所有的数字,让我们来看看使用正则是如何实现的。

# 在这里创建待处理的一句话
my_text = '我有3本书,这3本书一共有1800页'
 
# 在这里编写正则表达式的语法,这个语法是由字符串构成
regular = '[0-9]+'
 
# 使用 re 模块的 findall 函数获取正则语法对应的内容
result = re.findall(regular,my_text)
 
# 输出结果
print(result)

运行代码,输出如下结果,可见这句话中所有的数字都被查找到并以列表的方式输出了出来。

['3', '3', '1800']

在上面的示例代码中,实现了通过编写正则表达式规则,从一段话中提取全部的数字内容,在这段代码中,有两个关键点,一个是 编写正则规则 ,另一个是 选择功能函数

上面代码中的第 3 行,就是 编写正则规则 过程,这个规则同样也是由字符串构成的,根据我们的需求来编写,以上面代码为例,其中的 [0-9] 代表选择数字内容,后面接的 + 符号代表选择 1 个或者多个前面的元素,也就是选择至少一个数字。

上面代码中的第 9 行,是 选择功能函数 过程,在我们编好正则的规则后,需要决定用这个规则做什么,你可以判断一段话和正则表达式是否匹配、也可以将正则规则匹配到的部分替换成新的内容、也可以像上面案例中将匹配到的内容用列表的形式输出出来。

上面这两点是掌握好正则表达式的基础,在接下来的教程中我会详细为大家进行讲解。


编写正则表达式规则

编写正则表达式规则是正则学习过程中最核心也是最难学的部分,正则表达式规则是一个 “规则字符串”,这个规则由以下的几种情况构成,分别是 “普通字符”“元字符”“重复限定符”“条件符”“区间符”“转义符” 这几种类型。

要注意的是,在正则规则的编写中,上面的几种类型往往会经常组合使用。

① 普通字符

普通字符是最基础的正则规则,在正则表达式中就代表他们本身,没有特殊含义,比如,正则规则中的 a 会对应找到文本中的 a ,正则中的数字 3 ,会找到文本中的数字 3

# 在这里创建待处理的一句话
my_text = '牧旗教程的宗旨是帮助新手小白快速掌握编程技术'
 
# 在这里编写正则表达式的语法,这个语法是由普通字符构成
regular = '牧旗教程'
 
# 使用 re 模块的 findall 函数获取正则语法对应的内容
result = re.findall(regular,my_text)
 
# 输出结果
print(result)

运行代码,在 my_text 中找到了 牧旗教程 这个短语的本身。

['牧旗教程']

② 元字符

元字符是构造正则表达式规则中的一种特殊元素,它们可以用来进行复杂的匹配操作。

下面让我们来看一些非常常用的元字符:

符号 描述
. 英文的句号,用于匹配除了换行符以外的任何字符,可以理解为匹配几乎所有的内容
\w 用于匹配任意字母(大写和小写)、数字、下划线、汉字,用于构成一个单词或短语。
\s 用于匹配任意的空白符。
\d 用于匹配任意的数字。
\b 用于匹配单词的开始或者结束。通常用于捕捉一个完整的单词。
^ 用于在字符串的开始位置进行匹配。
$ 用于在字符串的结尾的位置进行匹配。

有了元字符这个概念,就可以通过正则完成一些通用性较强的匹配逻辑。

为了方便大家理解,在下面的案例中我将继续使用 findall 这个选择功能函数,其作用是将匹配到的内容以列表的形式输出出来。

让我们来看关于元字符的一些案例来帮助理解:

# 在这里创建待处理的一句话
my_text = '牧旗教程的创立于2023年4月,目前日活跃用户超过8000人。'
 
# 我的目的是找到这段话中的所有四位数字
regular = '\d\d\d\d'
 
# 使用 re 模块的 findall 函数获取正则语法对应的内容
result = re.findall(regular,my_text)
 
# 输出结果
print(result)

上面的代码中,正则表达式由 4 个 \d 构成,其目的是寻找待处理文本中的四位数字内容。

运行代码,结果如下,我们可以发现,文本中的所有数字都被找到并输出成一个列表。

['2022', '8000']

接下来我在正则表达式中新增一个位置匹配符,告诉正则表达式我要提取什么位置的数据:

# 在这里创建待处理的一句话
my_text = '2022年4月牧旗教程开始编写,截止到现在,教程的日活跃用户超过8000'
 
# 我的目的是找到这段话中的所有四位数字,并且这个数字要在开头的位置
regular1 = '^\d\d\d\d'
 
# 我的目的是找到这段话中的所有四位数字,并且这个数字要在结尾的位置
regular2 = '\d\d\d\d$'
 
# 使用 re 模块的 findall 函数获取正则语法对应的内容
result1 = re.findall(regular1,my_text)
result2 = re.findall(regular2,my_text)
 
# 输出结果
print(result1)
print(result2)

上面的代码中,^ 符号代表从开头的位置开始匹配,如果开头的位置没有找到,那么不会返回结果,同理,$ 符号是从结尾的位置开始定位。

运行上面的代码,结果如下,开头位置的四位数字和结尾位置的四位数字都被查找了出来。

['2022']
['8000']

使用元字符可以完成很多功能,比如匹配 abc 开头的字符串可以用如下规则:

regular = '^abc'

如果想匹配11位的电话号码,并以1开头可以用如下规则:

regular = '^1\d\d\d\d\d\d\d\d\d\d$'

③ 重复限定符

在使用元字符的时候经常会出现重复编写的情况,比如要匹配11位电话号码就要重复写11遍 \d ,这样代码看上去乱七八糟的,,所以为了处理这些重复的符号,正则表达式中可以使用重复限定符,把重复部分用合适的限定符替代。

下面让我们了解一下正则表达式中的重复限定符:

符号 描述
* 重复前面的字符零次或更多次,通俗解释就是有几个都行,没有也行。
+ 重复前面的字符一次或者更多次,通俗解释就是一定要有,有几次无所谓。
重复前面的字符一次或者零次。通俗理解就是有或没有,如果有只能是一个。
{n} 重复匹配前面字符 n 次。
{n,} 重复匹配前面字符 n 次以上。
{n,} 重复匹配前面字符 n 次到 m 次。

有了上面这些重复限定符之后,我们就可以精简已经学会的元字符,比如上面提到的匹配1开头的11位电话号码就可以改成如下写法:

regular = '^1\d{10}$'

来看一个简单的例子,在英文中 color 和 colour 都是代表 “颜色” 的意思,那么可以用以下的表达式来匹配这个单词:

my_text = '在英文中,颜色这个单词有两种写法,分别是 color 和 colour'
 
# 我的目的是找到这段话中的 colour 或者 color
regular = 'colou?r'
 
# 使用 re 模块的 findall 函数获取正则语法对应的内容
result = re.findall(regular,my_text)
 
# 输出结果
print(result)

在上面的代码中,我们需要匹配的两个单词区别是是否有 'r' 这个字母,所以在构造正则规则的时候使用 ?这个重复限定符来进行限制,通俗理解就是有没有 'r' 这个字母都行,如果有的话只能是一个。

让我们运行上面的代码来查看结果,结果如下:

['color', 'colour']

④ 条件符

当然,我们在进行匹配的时候会遇到多种不确定的情况,比如我们希望文本匹配到不确定的内容时,需要用 “或” 条件符来进行编写。

正则用符号 | 来表示或,也叫做分支条件,并且或的内容要用括号括起来,当满足正则里的多个条件的任何一种条件时,都会当成是匹配成功。

来看一个案例,我希望在一段文本中匹配到以 .com 或 .cn 或 .org 作为结尾的邮箱,让我们来看看正则规则应该如何编写:

my_text = '123456789@qq.com'
 
# 我的目的是找到这段话中以com 或 cn 或 org作为结尾的内容
regular = '.*(com|cn|org)$'
 
# 使用 re 模块的 findall 函数获取正则语法对应的内容
result = re.findall(regular,my_text)
 
# 输出结果
print(result)

此时,无论文本的结尾中出现 com 还是 cn 还是 org 都将被正则抓取到,这样可以在不确定邮箱格式的情况下完成抓取,运行上面的代码,结果如下:

['com']

⑤ 区间符

正则表达式中的区间符是一种用于匹配特定字符范围的符号,可以根据范围来定位数字或者字母,使用方括号 [ ] 来将想要的范围写入。

要注意,如果匹配的是连续的字符,那就只需要在方框内写入对应的开头和结尾即可,如下:

# 用下面的正则规则可以找到从数字2到数字8之间的全部数字
regular = '[2-8]'
 
# 用下面的正则规则可以找到从字母d到字母k之间的全部字母
regular = '[d-k]'
 
# 用下面的正则规则可以找到从字母D到字母K之间的全部大写字母
regular = '[D-K]'

另外,我们也可以将多个字符范围进行组合使用,可以在方括号中将多个范围联合进行编写,比如我希望同时提取到指定数字或字母,可以将规则写在一起:

# 用下面的正则规则可以找到从字母a到z以及数字0到9的全部内容
regular = '[a-z0-9]'

⑥ 转义符

我们已经知道在正则表达式中有很多符号具备特殊的含义,比如 * 代表选择全部文本内容,但是如果我需要捕捉到 * 符号本身呢?这个时候就需要用到转义符来取消各种符号的特殊含义,比如我希望抓取到 ? 这个字符本身,需要在问号前面写上反斜杠:

my_text = '牧旗教程一共由几个字构成?'
 
# 我的目的是找到这段话中的 “?” 符号
regular = '\?'
 
# 使用 re 模块的 findall 函数获取正则语法对应的内容
result = re.findall(regular,my_text)
 
# 输出结果
print(result)

在上面的代码中,如果我的正则规则直接使用 ? 符号,将不会捕捉到我们想要的内容,反而会产生错误信息,因为 ? 在正则表达式中代表捕捉前面的字符一次或者零次,所以使用 \? 这样的写法取消 “?” 这个符号的特殊含义,改为捕捉其本身。


使用正则表达式函数

正则表达式函数是配合正则规则所设计的功能函数,如果把正则表达式规则比喻成乐谱,那么正则的函数就是乐器,根据使用者的不同需求,来对同一首乐曲选择不同的乐器进行演奏。

在上面的教程中,我们一直在使用 re.findall 这个内置函数来获取提取到的数据,其功能是将符合正则规则的全部内容全都抓取并用一个列表的形式呈现。当然正则表达式的函数除了这个功能以外,还有很多其他功能可以实现,让我们来深入了解一下:

中文含义 函数 描述
从字符串开头匹配 re.match 从字符串的 开头开始 匹配正则规则,并返回一个匹配对象。
搜索字符串 re.search 整个 字符串中搜索正则规则的第一个匹配项,并返回一个匹配对象。
查找所有匹配项 re.findall 查找字符串中所有匹配正则表达式的子串,并 以列表的形式 返回它们。
替换内容 re.sub 替换字符串中匹配正则规则的部分,可选择性地指定替换的最大次数。
编译正则表达式 re.compile 编译正则表达式,返回一个可重复使用的正则表达式对象。
拆分字符串 re.split 根据正则表达式的匹配项拆分字符串,并返回拆分后的子串列表。

下面让我们结合案例,对上面的规则函数一一进行学习:

① 从字符串开头匹配

re.match 函数的具体写法是 re.match(pattern, string, flags=0),其中 pattern 是正则表达式的规则,string 是待匹配的字符串,flags 是一个可选参数,在后面的教程中会详细讲解。

这个函数的功能是在目标字符串的开头查找是否存在与正则表达式匹配的内容。如果找到匹配,它将返回一个匹配对象;否则,返回 None。来看下面的案例:

# 在使用前要首先导入 re 这个模块
import re
 
# 构造正则表达式规则
pattern = '牧旗'
 
# 构造一段待处理的文本内容
my_text = '牧旗教程创建的目的是为了让更多初学者能够更好地理解编程的技巧'
 
# 使用 re 模块的 match 函数来判断正则规则是否能在字符串开头的位置匹配到
result = re.match(pattern,my_text)
 
# 如果查找到,就输出内容
if result:
    print(pattern + '出现在了开头')
else:
    print(pattern + '没有在开头找到')

运行上面的代码,可以看到结果如下:

牧旗出现在了开头

结果是 “牧旗” 这两个字在文本的开头出现了,所以就被匹配到,如果 “牧旗” 这两个字出现在了这句话中中间,那么输出的结果将是没有找到。

② 搜索字符串

与 re.match 函数不同的是,搜索字符串函数会在整个字符串中进行搜索,凡是在整个文本中出现符合正则规则的内容,就会被抓取到,如果整个文本中都没有对应的内容,则会返回空值 None 。

re.search() 函数的具体写法是 re.search(pattern, string, flags=0),其中 pattern 是正则表达式的规则,string 是待匹配的字符串。让我们来看一个案例:

# 在使用前要首先导入 re 这个模块
import re
 
# 构造正则表达式规则,找到所以的数字内容
pattern = '[0-9]+'
 
# 构造一段待处理的文本内容
my_text = '牧旗教程创立于2023年,目前日活人数已经达到了8000人'
 
# 使用 re 模块的 match 函数来判断正则规则是否能在字符串开头的位置匹配到
result = re.search(pattern,my_text)
 
# 如果查找到,就输出内容
if result:
    # 输出定位到的元素的第一个匹配项的位置索引
    print(result.start())
    # 输出定位到的元素的最后一个匹配项的位置索引
    print(result.end())
    # 如果查找到,就输出内容
    print(result.group())
else:
    print(pattern + '没有在开头找到')

注意上面的代码,我使用了 re.search 这个函数在整个文本中进行查询,对于返回的结果 result ,可以使用 start() 或 end() 函数分别查看定位到的第一个内容和最后一个内容在文本中的索引位置,使用 group() 函数可以查看匹配到的第一个元素的具体内容。

上面代码的运行结果如下:

7
11
2022

③ 查找所有匹配项

这个函数我们应该已经比较熟悉了,在上面的正则规则案例中我们就是使用 re.findall() 函数来将所有符合的内容输出到一个列表中,这个函数的写法是 re.findall(pattern, string, flags=0) ,与前面的函数规则相同,在正则规则的介绍中也有大量案例,因此在此不再展开说明。

④ 替换内容

re.sub() 函数用于在字符串中查找正则表达式模式的匹配项,并将其替换为指定的字符串。该函数的写法是 re.sub(pattern, repl, string, count=0, flags=0) ,其中 pattern 是正则表达式模式,用于匹配要替换的文本,repl 是用于替换匹配项的字符串;string 是要搜索和替换的目标字符串,count是一个可选参数,用于指定替换的最大次数。默认值为0,表示替换所有匹配项。

让我们直接通过案例来深入了解一下这个替换函数:

# 在使用前要首先导入 re 这个模块
import re
 
# 构造正则表达式规则,找到所以的数字内容
pattern = r'[0-9]+'
 
# 构造一段待处理的文本内容
my_text = '您好,这里是牧旗教程,我们已经成立1年啦!'
 
# 将文本中的数字部分找到并进行替换
result = re.sub(pattern,'X',my_text)
 
print(result)

运行上面的代码,会得到以下的结果,可以发现原文中的数字 1 被替换成了 X 字符:

您好,这里是牧旗教程,我们已经成立X年啦!

在使用 re.sub 替换函数的时候一定要注意传入的参数的顺序,第一个参数是正则表达式、第二个参数是要替换的新内容、第三参数是被替换的全部文本,顺序一定不要混乱掉。

⑤ 编译正则表达式

编译正则表达式用到了 re.compile() 函数,它的作用是可以将正则表达式编译为一个可重复使用的正则表达式对象,可以提高正则表达式的执行效率,并可以多次重复使用。

下面让我们看看这个函数的实际创建方式和用法:

# 在使用前要首先导入 re 这个模块
import re
 
# 创建一个正则表达式对象
pattern = re.compile(r'[0-9]+')
 
# 在上面的位置生成了一个 正则表达式 对象,这个对象有各种功能和函数
# 下面我直接调用这个正则对象来进行查找,写法如下:
result = pattern.findall('您好,这里是牧旗教程,我们已经成立1年啦')
 
print(result)

运行上面的代码,结果如下,可以看出和直接使用 re.findall 结果是一样的:

['1']

使用 re.compile() 的主要好处是,如果你需要多次执行相同的匹配操作,可以重复使用已编译的正则表达式对象,而不必每次都重新编译模式,这可以提高性能。

当你需要在代码中多次使用相同的正则表达式模式时,使用 re.compile() 是一种优化的方式,因为它可以减少不必要的重复编译操作。

⑥ 拆分字符串

编译正则表达式用到了 re.split() 函数,使用正则表达式拆分字符串是一种在特定模式或模式集合下将字符串分割成子字符串的方法,结果会形成一个新的列表。

下面让我们看看这个函数的实际创建方式和用法:

# 在使用前要首先导入 re 这个模块
import re
 
# 创建一个正则表达式对象
pattern = r'[a-z]+'
 
# 将一段文本按照英文进行分割
result = re.split(pattern,'苹果and草莓and菠萝and梨')
 
print(result)

运行上面代码,结果如下:

['苹果', '草莓', '菠萝', '梨']

关注公众号【牧旗教程】,回复“更多例题”,获取更多题型进行训练~

您的打赏将帮助维护网站服务器的正常运营,并为作者的后续更新提供更多的动力。

无法显示 无法显示