python-字符串全解(六):正则表达式-分组

这一章我们讲分组,分组在正则表达式中是一个很有用的工具。它主要有以下的作用:重复,缩小逻辑运算的范围,内容提取,反向引用,通过命名读取内容等。如果你之前没有学习过分组,这么一看这些作用说明,肯定没法做到顾名思义,甚至会觉得有点迷茫。放心,就像我之前说过,很多东西你只要一旦明白,就会觉得非常的简单,如果你读过我之前正则表达式的其他章节,你肯定深有体会,分组也一样,接下来我们会详细的谈论分组。

6.分组

分组主要是用圆括号包裹起来的语法,里面可以包含正则匹配的语法,同时还有一些分组特有的语法。主要有:捕获组,可用于提取内容(...),非捕获组,不提取内容(?:...),命名捕获组(?P<name>...),引用命名组(?P=name),引用组通过序号例如/1,/2。

6.1重复以及内容提取((...))

在前面讲的语法的时候,我们提到了重复,我们可以通过*,+,?,{m,n}这样的语法来重复前面的字符,但是我们需要重复的字符不是一个而是多个呢?这时候就需要用到分组双括号(),比如(abc)+,这个代表abc可以重复一次或者多次,从而可以匹配abcabcabc这样的字符串。如果在()中开始位置加入?:表示不提取内容。看一下下面的例子:

python 复制代码
import re
a = "abcabc23abcabc12ac1123abc31123abc"
b = re.findall(r"(abc)+\d+", a)
b1 = re.findall(r"(?:abc)+\d+", a)
c = re.search(r"(abc)+\d+", a)
c1 = re.search(r"((abc)+(\d+))", a)
print("a= {}, b = {}, b1 = {}, c = {}, c.group() = {},  c.group(1) = {} \
 c1.group(0) = {}, c1.group(1) = {}, c1.group(2) = {}, c1.group(3) = {}".
      format(a, b, b1, c, c.group(), c.group(1), c1.group(0),
             c1.group(1), c1.group(2), c1.group(3)))
# 输出结果:a= abcabc23abcabc12ac1123abc31123abc, 
# b = ['abc', 'abc', 'abc'], b1 = ['abcabc23', 'abcabc12', 'abc31123'], 
# c = <re.Match object; span=(0, 8), match='abcabc23'>, 
# c.group() = abcabc23,  c.group(1) = abc  c1.group(0) = abcabc23, 
# c1.group(1) = abcabc23, c1.group(2) = abc, c1.group(3) = 23

b中我们用圆括号包裹了abc,然后在括号后面跟了一个+,表示abc至少重复一次,后面跟了\d+,表示数字至少出现一次。我们看b的结果,有点奇怪,怎么都是abc?这是因为findall这个函数只是提取了括号里面的内容。对比一下b1,结果就是比较符合预期。b1在原括号中开始位置加入了?:表示不提取括号内容。b1结果的第一个成员abcabc23,其中abc至少出现了两次,我们看b的结果中,都是abc,原因是()只提取重复的最后一次,所以只有abc,这个abc指的是abcabc23中的第二个abc。

c我们用了一个全新的函数search,这个函数在字符串中找到一个匹配就停止了。打印c,发现c是一个Match对象,这个对象最常用的函数就是group,默认group()返回匹配整个正则的串,其实和group(0)效果是一样的。这个正则表达式中有多少个分组(),后面就可以跟最大数字是这个分组的个数。c1有三个分组,那么group(1)代表第一个分组,group(2)代表第二个分组,group(3)代表第三个分组。分组是从左到右计算,第一个左括号(算第一个,第二个左括号(算第二个,就算括号之间嵌套也没关系。c1的group(0)和group(1)结果是一样的,原因是1把整个正则表达式都包含进来了,所以结果也是匹配整个正则的值,group(1)和group(2)分别是包含了字母和数字。

6.2限制逻辑运算作用域

逻辑运算我们还没有讲到,我们用|表示或,我们看一下下面的代码:

python 复制代码
import re
a = "abc2342adc1123abc31123abc"
b = re.findall(r"abc\d{1,9}|adc\d{1,9}|abc3abc\d{1,9}",a)
c = re.findall(r"(?:abc|adc|abc)\d{1,9}", a)

print("a= {}, b = {}, c = {}".
      format(a, b, c))
# 输出结果:a= abc2342adc1123abc31123abc, 
# b = ['abc2342', 'adc1123', 'abc31123'], 
# c = ['abc2342', 'adc1123', 'abc31123']

b是用我们之前的方式,我们为了用逻辑或,需要将每种可能就完整的写下来,这样显得繁琐,因为每个或的正则都有共通的\d{1,9},我们可以全部提取到外面,这就需要用到分组圆括号,这样这个或的逻辑运算只是被限制在圆括号内,不会影响括号外的正则,如c1所示。因为findall只捕获圆括号中的内容,所以可以看到我们用?:来阻止捕获内容,这样findall可以得到完整的结果。

6.3命名捕获组((?P<name>...))

可以(?P<name>...)这样的形式来给捕获组命名,注意P是大些的P,这样我们可以在group参数里面通过名字来引用,这样做的好处是比没有意义的数字好记。看一下下面的代码:

python 复制代码
import re
a = "abcabc23abcabc12ac1123abc31123abc"
b = re.search(r"(?P<word>(?P<letter>abc)+(?P<number>\d+))", a)
print("b = {}, b.group(1) = {},  b.group(2) = {}, b.group(3) = {} \
 b.group(word) = {}, b.group(letter) = {}, b.group(number) = {}".
      format(b, b.group(1), b.group(2), b.group(3),
             b.group("word"), b.group("letter"), b.group("number")))
# 输出结果:b = <re.Match object; span=(0, 8), match='abcabc23'>, 
# b.group(1) = abcabc23,  b.group(2) = abc, b.group(3) = 23  
# b.group(word) = abcabc23, b.group(letter) = abc, b.group(number) = 23

b中我们将三个分组都做了命名,最外层的分组命名为word,这包含了剩下的两个分组。另外两个分组分别命名为letter和number。打印的结果来看,group中输入名字和输入分组的位置,都可以得到相同的结果,只是命名之后更清晰明白,增加了可读性。

6.4分组引用((?P=name), \number, number是分组的位置)

上一节我们讲了分组,我们可以在正则表达式中引用这个分组,可以通过命名引用?P=name),也可以通过分组位置引用,用\加上分组位置。看一下下面的代码:

python 复制代码
import re
a = "abc12abcabc123abc"
b = re.search(r"(?P<letter>abc)\d+(?P=letter)", a)
b1 = re.search(r"(abc)\d+\1", a)

print("b = {}, b.group() = {},  b.group(1) = {} \
 b1.group() = {}, b1.group(1) = {}".
      format(b, b.group(), b.group(1),
             b1.group(), b1.group(1)))
# 输出结果:b = <re.Match object; span=(0, 8), match='abc12abc'>, 
# b.group() = abc12abc,  b.group(1) = abc  
# b1.group() = abc12abc, b1.group(1) = abc

b首先命名了一个分组为letter,这个分组包含了字母abc,然后跟了\d+表示至少有一个数字,然后后引用了这个分组,b1是用\1实现了同样的作用。我们可以看到结果是一样的。这里有一点要注意(?P=letter)这只是引用分组,但是它本身并不是分组,也就是说b的正则表达式中只有一个分组,所以如果你b.group(2)会提示没有这个分组。如果你要把它当作一个分组,需要再加一对圆括号,如下面的代码所示:

python 复制代码
import re
a = "abc12abcabc123abc"
b = re.search(r"(?P<letter>abc)\d+((?P=letter))", a)
b1 = re.search(r"(abc)\d+(\1)", a)

print("b = {}, b.group() = {},  b.group(1) = {}, b.group(2) = {} \
 b1.group() = {}, b1.group(1) = {}, b.group(2) = {}".
      format(b, b.group(), b.group(1), b.group(2),
             b1.group(), b1.group(1), b1.group(2)))
# 输出结果:b = <re.Match object; span=(0, 8), match='abc12abc'>, 
# b.group() = abc12abc, b.group(1) = abc, b.group(2) = abc  
# b1.group() = abc12abc, b1.group(1) = abc, b.group(2) = abc

b和b1都多加了一个括号,都有了两个分组,这样就可以通过group(2)来获取分组的内容了。

讲到这里,我们的分组就讲完了。分组在重复多个字符还是很有用的,另外分组还可以命名并且被引用,这增加了程序的可读性,下一章我们会介绍修饰符,包含全局和分组修饰符。

相关推荐
前端精髓20 小时前
结构化输出with_structured_output(抽卡)
python·langchain
小此方20 小时前
Re:Mysql数据库基础篇(八):详细讲解复合查询、多表查询、自连接、多行、多列子查询与合并查询
数据库·mysql
smile_life_20 小时前
MySQL 设置 防止账户被暴力破解
数据库·mysql
尹人入圣20 小时前
目前热门的IP驱动产业新场景新工具
网络·python·网络协议·tcp/ip
滕州市燕猫虎计算机科技工作室个体工商户21 小时前
MySQL、MVCC、多版本并发控制
数据库·mysql·mvcc·数据库事务
Nuanyt21 小时前
Agent 学习笔记(下篇三):上线工程、进阶选择与操作界面(Stage 7 / 7.5 / 8 + 延伸路线)
笔记·python·学习·memory·aiagent·harness
吴卫斌21 小时前
定投ETF实战指南(四):行业/主题ETF——定投的“进攻端“
开发语言·python·股票·量化
2601_967987371 天前
Python——入门级(定义功能def函数)
python·函数·可变参数·默认参数·关键字参数
Decay__1 天前
09_MySQL 闪回与时间点恢复 —— binlog 回放、mysqlbinlog、PITR 与误删恢复
运维·数据库·sql·mysql·架构
太理摆烂哥1 天前
MySQL用户管理
数据库·mysql