1、分组匹配
Python中进行匹配操作时,在正则表达式中加入括号“()”,可以对匹配到的字符串进行分组,目的是分离我们匹配到的字符串。使用group()方法可以获取匹配的所有内容,也可以获取匹配结果中指定分组的内容,另外,还可以通过groups()方法把所有匹配出来的分组加入到元组中。例如:
动手练一练:
import re
a1 = r"(\w+)\s+(\d+)" # 分离姓名和电话号码
b1 = "Maria 123456789"
result = re.match(a1, b1)
name = result.group(1)
phone = result.group(2)
print(f"名字: {name}") # 输出 名字: Maria
print(f"电话号码: {phone}") # 输出 电话号码: 123456789
print(result.group()) # 输出 Maria 123456789
print(result.groups()) # 输出 ('Maria', '123456789')
c1 = re.findall(a1, b1)
print(c1) # 输出 [('Maria', '123456789')]
执行以上代码,输出结果为:
名字: Maria
电话号码: 123456789
Maria 123456789
('Maria', '123456789')
[('Maria', '123456789')]
上面的例子中,“(\w+)”代表匹配一个或多个字母、数字或下划线,表示姓名。“\s+”代表匹配一个或多个空格字符,表示姓名和电话号码之间的间隔。“(\d+)”代表匹配一个或多个数字,表示电话号码。使用group(1)来获取第一个括号匹配的结果,使用group(2)来获取第二个括号匹配的结果。这里需要注意的是,re.match()函数和re.search()函数会返回一个对象,必须通过group()函数提取数据,而re.findall()函数会直接返回一个包含所有分组结果的列表。
正则表达式中分组匹配的本质是,将匹配的内容里一部分抠出来,如果抠出的内容不止一个,就放到元组里面。例如:
动手练一练:
import re
#分组匹配
a = """
1张三,今年15岁。
2李四,今年16岁。
3王五,今年17岁。
4赵六,今年18岁。
"""
#选择每行逗号前面的字符串,不包括逗号
b = re.compile(r"^(.*),", re.M) # “^”匹配字符串的开头,“re.M”实现多行匹配
result = b.findall(a)
print(result)
#选择每行逗号前面的字符串,包括逗号并分组存储
b = re.compile(r"^(.*)(,)", re.M)
result = b.findall(a)
print(result)
执行以上代码,输出结果为:
['1张三', '2李四', '3王五', '4赵六']
[('1张三', ','), ('2李四', ','), ('3王五', ','), ('4赵六', ',')]
上面的例子中,正则表达式中的re.M参数表示将字符串视为多行,从而“^”匹配每一行的行首,“$”匹配每一行的行尾。
2、贪婪与非贪婪匹配
贪婪匹配:在Python中,正则表达式默认使用贪婪匹配模式,即尽可能多地匹配字符串。例如:
动手练一练:
import re
s = "acbab"
p = re.compile(r"a.*b")
result = p.findall(s)
print(result) # 输出: ['acbab']
执行以上代码,输出结果为:
['acbab']
上面的例子中,“a.*b”将会匹配最长的以a开始,以b结束的字符串。如果用它来搜索“acbab”的话,它会匹配整个字符串“acbab”,这被称为贪婪匹配。
非贪婪匹配:也就是尽可能少地匹配字符串。在前面的教程中,我们已经介绍了正则表达式提供的限定符包括“*”、“+”、“?”、“{n}”、“{n,}”和“{n, m}”,只要在这些限定符的后面加上问号“?”,就可以进行非贪婪匹配。例如:
动手练一练:
import re
s = "acbab"
p = re.compile(r"a.*?b")
result = p.findall(s)
print(result) # 输出: ['acb', 'ab']
执行以上代码,输出结果为:
['acb', 'ab']
上面的例子中,星号“*”后面加上问号“?”就变为非贪婪模式,此时会匹配尽可能少的字符。在“a.*?b”中,可以匹配最短的,以a开始,以b结束的字符串。如果用它来搜索“acbab”的话,它会匹配“acb”(第一到第三个字符)和“ab”(第四到第五个字符),因为它会匹配尽可能少的字符,所以不会匹配“acbab”。
下表列出了非贪婪匹配的常见语法:
用法\语法 | 功能 |
---|---|
*? | 重复任意次,但尽可能少重复 |
+? | 重复1次或更多次,但尽可能少重复 |
?? | 重复0次或一次,但尽可能少重复 |
{n,m}? | 重复n到m次,但尽可能少重复 |
{n,}? | 重复n次以上,但尽可能少重复 |
3、零宽断言
在程序开发中使用正则表达式做匹配的时候,我们希望匹配一个字符串,这个字符串的前面或后面是一个特定的内容,但我们又不想要前面或后面的这个特定的内容,这个时候就要借助正则表达式中的零宽断言。零宽断言,简单来说就是用于指定一个位置,这个位置应该满足一定的条件,但是这个位置又不纳入匹配的结果,所以叫“零宽”。
举个简单的例子,比如我们打开任意一个网页,在网页的空白部分右键单击鼠标,然后从弹出的菜单中选择“查看网页源代码”,在html网页源代码中,都有“<title>标题内容</title>”这样的标题标签,用前面介绍的正则表达式方法,我们只能确定源码中的<title>和</title>是固定不变的。因此,如果想获取页面title标签中的“标题内容”,最简单的方法只能这样写代码:<title>.*</title>,而这个方法匹配出来的是一个完整的“<title>标题内容</title>”标签,并不是单纯的“标题内容”。想解决这个问题,就需要用到零宽断言的方法。
用法\语法 | 功能 |
---|---|
?=exp | 简称正向先行断言,它断言此位置的后面能匹配表达式“exp”,但不包含此位置,例如“a regular expression”这个字符串,要想匹配“regular”中的“re”,但不能匹配“expression”中的“re”,可以用”re(?=gular)”,该表达式限定了“re”右边的位置,这个位置之后是“gular”,但并不想要“gular”这些字符。 |
?<=exp | 简称正向后发断言,它断言此位置的前面能匹配表达式“exp”,例如“regex represents regular expression”这个字符串里面有4个单词,要想匹配某个单词里面的“re”,但不匹配某个单词开头的“re”,可以用“(?<=\w)re”,该表达式规定在“re”前面必须有单词字符。 |
?!exp | 简称反向先行断言,它断言此位置的后面不能匹配表达式“exp”,例如“regular expression”这个字符串,要想匹配除“regular”之外的“re”,可以用“re(?!g)”,该表达式限定了“re”右边的位置,这个位置后面不能有字母“g”,这里的感叹号“!”就是表示非,不需要的意思。 |
?<!exp | 简称反向后发断言,它断言此位置的前面不能匹配表达式“exp”,例如“regex represents regular expression”这个字符串,要想匹配某个单词开头的“re”,可以用“(?<!\w)re”。该表达式规定在“re”前面不能有单词字符。感叹号“!”表示非,不需要的意思,当然也可以用”\bre”来匹配。 |
如果不用零宽断言,普通的正则表达式,仅仅能获取到有规律的字符串,而不能获取无规律的字符串。例如:
动手练一练:
import re
a = "<title>标题内容</title>"
#匹配<title>标签里面的“标题内容”
b = re.compile(r"(?<=<title>).*(?=</title>)")
result = b.search(a)
print(result.group()) # 输出 标题内容
执行以上代码,输出结果为:
标题内容
上面的例子中,我们想要的是“标题内容”,它没有规律,但是它前边肯定会有<title>标签,后边肯定会有</title>标签,这就足够了。“标题内容”前肯定会出现<title>,就用正向后发断言,表达式:“(?<=<title>)”。“标题内容”后边肯定会出现</title>,就用正向先行断言,表达式:“(?=</title>)”。
下面是一些简单的示例:
动手练一练:
import re
# 前面必须是“abc”才能匹配后面写的内容
print(re.search(r"(?<=abc)\d+", "abc123xyz").group()) # 输出: 123
# 后面必须是“xyz”才能匹配前面写的内容
print(re.search(r"(\d+(?=xyz))", "abc123xyz").group()) # 输出: 123
# 前面必须是“abc”,后面必须是“xyz”才能匹配之间写的内容
print(re.search(r"(?<=abc)\d+(?=xyz)", "abc123xyz").group()) # 输出: 123
# 前面不是“xyz”才能匹配后面写的内容
print(re.search(r"(?<!xyz)\d+", "abc123xyz").group()) # 输出: 123
# 前面不是“xyz”才能匹配后面写的内容,利用“?”限制贪婪
print(re.search(r"(?<!xyz)\d+?", "abc123xyz").group()) # 输出: 1
# 后面不是“abc”才能匹配后面写的内容
print(re.search(r"\d+(?!abc)", "abc123xyz").group()) # 输出: 123
执行以上代码,输出结果为:
123
123
123
123
1
123