【Python教程】flag匹配模式和分组功能

匹配模式

Python的re模块提供了一些可选的标志修饰符来控制匹配的模式。可以同时指定多种模式,通过与符号|来设置多种模式共存。如re.I | re.M被设置成I和M模式。

匹配模式 描述
re.A ASCII字符模式
re.I 使匹配对大小写不敏感,也就是不区分大小写的模式
re.L 做本地化识别(locale-aware)匹配
re.M 多行匹配,影响 ^ 和 $
re.S 使 . 这个通配符能够匹配包括换行在内的所有字符,针对多行匹配
re.U 根据Unicode字符集解析字符。这个标志影响 \w, \W, \b, \B
re.X 该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解

re.I模式:

正则表达式是区分字母大小写的,但在re.I模式下,则忽略大小写。

split()方法用flag时的问题,尝试运行代码re.split(‘a’,’1A1a2A3′,re.I),但是输出结果并不是我们预想的不区分大小写的a,将字符串分割。这是因为re.split(pattern,string,maxsplit,flags)方法的定义体中默认是四个参数,当我们传入三个参数的时候,re.I实际是被当做第三个参数传递进去了,所以没起到该有的作用。如果想让这里的re.I起作用,应当写成flags=re.I,也就是re.split(‘a’,’1A1a2A3′,flags=re.I)。

re.M模式:

多行匹配模式。默认,元字符^会匹配字符串的开始处,元字符$会匹配字符串的结束位置和字符串后面紧跟的换行符之前(如果存在这个换行符)。如果指定了这个选项,则^将会匹配字符串的开头和每一行的开始处,紧跟在每一个换行符后面的位置。类似的,$会匹配字符串的最后和每一行的最后,在接下来的换行符的前面的位置。

>>> import re
>>> s = "\nabc\n"
>>> re.search(r"^abc$",s)
>>>
>>> re.search(r"abc$",s)
<_sre.SRE_Match object; span=(1, 4), match='abc'>
>>> re.search(r"^abc",s)
>>>
>>> re.search(r"abc",s)
<_sre.SRE_Match object; span=(1, 4), match='abc'>
>>>
>>> re.search(r"^abc$",s,re.M)
<_sre.SRE_Match object; span=(1, 4), match='abc'>
>>>
>>> re.search(r"^abc",s,re.M)
<_sre.SRE_Match object; span=(1, 4), match='abc'>
>>>
>>> re.search(r"abc$",s,re.M)
<_sre.SRE_Match object; span=(1, 4), match='abc'>

re.X模式:

Python的re模块还有一个很有趣的X模式,也就是VERBOSE。该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解。当该标志被指定时,在正则表达式字符串中的空白、tab、换行符将被忽略,除非该空白符在字符类中或在反斜杠之后;这可以让你更清晰地组织和缩进表达式。它也允许你将注释写入表达式,这些注释会被引擎忽略;注释用 “#”号来标识,不过该符号不能在字符串或反斜杠之后。巴拉巴拉一堆,你只需要记住它的两个作用:一是让冗长难懂的表达式更易读;二是给表达式加注释。

>>> pat = re.compile(r'''
\* # 转义一个星号
( #左括号代表一个 组的开始
[^\*]+ #捕获任何非星号的字符
) #右括号代表组的结束
\* #转义一个星号
''',re.VERBOSE)

>>> obj = pat.search("hi ,this is a *something* !")
>>> obj
<_sre.SRE_Match object; span=(15, 26), match='*something*'>
>>> obj.group()
'*something*'

re.U模式:

re.UNICODE是种兼容模式。在字符串模式下被忽略(默认模式),在字节类型模式下被禁止。我们知道,在Python3之后,string和bytes被独立成了两种不同的数据类型。在re模块中,不能用bytes去匹配string或者用string去匹配bytes,只能用string匹配string,bytes匹配bytes。下面是一个bytes匹配bytes的例子:

import re

s = "hello"
b = bytes(s, encoding="utf-8")
pat = bytes('he', encoding="utf-8")
print("字符串s:%s" % s)
print("字节类型b:%s" % b)
print("字节类型正则表达式pat:%s" % pat)
obj_s = re.search(pat, b)
print("匹配结果:%s" % obj_s.group())

运行结果:

字符串s:hello
字节类型b:b'hello'
字节类型正则表达式pat:b'he'
匹配结果:b'he'

当使用UNICODE模式时,将强制禁止使用bytes类型,一但使用将报错。在string类型中,UNICODE是默认设置。

obj_s = re.search(pat, b, re.U) #只贴出了修改了的部分

运行结果:

Traceback (most recent call last):
File "F:/Python/pycharm/201705/test.py", line 10, in <module>
obj_s = re.search(pat, b, re.U)
File "C:\Python36\lib\re.py", line 182, in search
return _compile(pattern, flags).search(string)
File "C:\Python36\lib\re.py", line 301, in _compile
p = sre_compile.compile(pattern, flags)
File "C:\Python36\lib\sre_compile.py", line 562, in compile
p = sre_parse.parse(p, flags)
File "C:\Python36\lib\sre_parse.py", line 866, in parse
p.pattern.flags = fix_flags(str, p.pattern.flags)
File "C:\Python36\lib\sre_parse.py", line 840, in fix_flags
raise ValueError("cannot use UNICODE flag with a bytes pattern")
ValueError: cannot use UNICODE flag with a bytes pattern
字符串s:hello
字节类型b:b'hello'
字节e类型正则表达式pat:b'he'

re.S模式:

让圆点.这个通配符能够从默认的不支持换行符,变得可以匹配换行符。这在我们使用网络爬虫,爬取HTML文本的时候,非常重要。我们都知道,比如技术文章,网络小说中,文本通常都是大段大段的,并且包含很多换行符,为了将整个文本爬取下来,我们常常使用(.+)匹配核心文本内容,看下面的例子:

import re

s = """
<p>水调歌头·明月几时有\n
宋代:苏轼\n
\n
丙辰中秋,欢饮达旦,大醉,作此篇,兼怀子由。\n
\n
明月几时有?把酒问青天。\n
不知天上宫阙,今夕是何年。\n
我欲乘风归去,又恐琼楼玉宇,高处不胜寒。\n
起舞弄清影,何似在人间?\n
转朱阁,低绮户,照无眠。\n
不应有恨,何事长向别时圆?\n
人有悲欢离合,月有阴晴圆缺,此事古难全。\n
但愿人长久,千里共婵娟。\n</p>
"""

ret = re.search(r'<p>(.+)</p>', s)
print(ret)
print(ret.group(1))

d2b5ca33bd131808

代码运行的结果不是我们想象的那样,根本就没匹配到任何东西。原因就是圆点默认不匹配换行符,导致整个匹配的失败。

解决方法就是使用re.S模式!如下所示替换代码中国的语句,运行后就能看到想要的结果。

ret = re.search(r'<p>(.+)</p>’, s, re.S)

d2b5ca33bd131908

其它效果同理,需要用print打印出结果

分组功能

Python的re模块有一个分组功能。所谓的分组就是去已经匹配到的内容里面再筛选出需要的内容,相当于二次过滤。实现分组靠圆括号(),而获取分组的内容靠的是group()、groups()和groupdict()方法,其实前面我们已经展示过。re模块里的几个重要方法在分组上,有不同的表现形式,需要区别对待。

例一:match()方法,不分组时的情况:

import re

origin = "hasdfi123123safd"
# 不分组时的情况
r = re.match("h\w+", origin)
print(r.group()) # 获取匹配到的整体结果
print(r.groups()) # 获取模型中匹配到的分组结果元组
print(r.groupdict()) # 获取模型中匹配到的分组中所有key的字典

d2b5ca33bd132213

以下同理,需要print打印出结果

例二:match()方法,有分组的情况(注意圆括号!)

import re

origin = "hasdfi123123safd123"
# 有分组
r = re.match("h(\w+).*(?P<name>\d)$", origin)
print(r.group()) # 获取匹配到的整体结果
print(r.group(1)) # 获取匹配到的分组1的结果
print(r.group(2)) # 获取匹配到的分组2的结果
print(r.groups()) # 获取模型中匹配到的分组结果元组
print(r.groupdict()) # 获取模型中匹配到的分组中所有key的字典

执行结果:

hasdfi123123safd123
asdfi123123safd12
3
('asdfi123123safd12', '3')
{'name': '3'}

分析一下上面的代码,正则表达式h(\w+).*(?P<name>\d)$中有2个小括号,表示它分了2个小组,在匹配的时候是拿整体的表达式去匹配的,而不是拿小组去匹配的。(\w+)表示这个小组内是1到多个字母数字字符,(?P<name>\d)中?

P<name>是个正则表达式的特殊语法,表示给这个小组取了个叫“name”的名字,?P<xxxx>是固定写法。在获取分组值的时候,group()和group(0)是对等的,都表示整个匹配到的字符串,从group(1)开始,分别是从左往右的小组序号,按位置顺序来。

有时候括号会存在嵌套情况,那怎么确定组的顺序1,2,3?要么用取名字的方法,要么就数左括号,第几个左括号就是第几个分组,例如(1(2,(3)),(4)),0表示表达式本身,不参加数左括号的动作。

例三,search()方法,有分组的情况:

import re

origin = "sdfi1ha23123safd123" # 注意这里对匹配对象做了下调整
# 有分组
r = re.search("h(\w+).*(?P<name>\d)$", origin)
print(r.group())
print(r.group(0))
print(r.group(1))
print(r.group(2))
print(r.groups())
print(r.groupdict())

执行结果:

ha23123safd123
ha23123safd123
a23123safd12
3
('a23123safd12', '3')
{'name': '3'}

表现得和match()方法基本一样。

例四,findall()方法,没有分组的情况:

import re

origin = "has something have do"
# 无分组
r = re.findall("h\w+", origin)
print(r)

执行结果:

['has', 'hing', 'have']
# 一切看起来没什么不一样

注意到了没有?我根本没有调用group相关的方法,因为findall()的返回值是个列表,根本就没有group()、groups()、groupdict()的概念!

例五,findall()方法,有一个分组的情况:

import re

origin = "has something have do"
# 一个分组
r = re.findall("h(\w+)", origin)
print(r)

执行结果:

['as', 'ing', 'ave']

相比较前面未分组的例子,有没有发现什么?对了!没有圈在分组内的内容被抛弃了,比如这里的字符’h’。

例六,findall()方法,有两个以上分组的情况:

import re

origin = "hasabcd something haveabcd do" # 字符串调整了一下
# 两个分组
r = re.findall("h(\w+)a(bc)d", origin)
print(r)

运行结果:

[('as', 'bc'), ('ave', 'bc')]

注意到了返回值是什么了吗?元组组成的列表!

例七,sub()方法,有分组的情况:

import re

origin = "hasabcd something haveabcd do"
# 有分组
r = re.sub("h(\w+)", "haha",origin)
print(r)

运行结果:

haha somethaha haha do

看到没有?sub()没有分组的概念!这是因为sub()方法是用正则表达式整体去匹配,然后又整体的去替换,分不分组对它没有意义。这里一定要注意了!

例八,split()方法,有一个分组的情况:

import re

origin = "has abcd something abcd do"
# 有一个分组
r = re.split("(abcd)", origin)
print(r)

运行结果:

['has ', 'abcd', ' something ', 'abcd', ' do']

事实上,在前面我们已经展示过这个例子,通过分组,我们可以拿到split匹配到的分隔符。

例九,split()方法,有两个分组,并且嵌套:

import re

origin = "has abcd something abcd do"
# 有一个分组
r = re.split("(a(bc)d)", origin)
print(r)

运行结果:
['has ', 'abcd', 'bc', ' something ', 'abcd', 'bc', ' do']

例十,split()方法,有多个分组,并且嵌套:

import re
origin = "has abcd something abcd do"
# 有一个分组
r = re.split("(a(b)c(d))", origin)
print(r)
运行结果:
['has ', 'abcd', 'b', 'd', ' something ', 'abcd', 'b', 'd', ' do']
© 版权声明
THE END
喜欢就支持一下吧
点赞7 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容