20.6 Re模块 正则表达式
python中正则表达式的实现内置模块re模块来实现。
.(点):匹配****任意********单个****字符(除换行符\n以外)[](中括号):匹配指定范围内的****任意********单个****字符(字符集合)[^](中括号中有个“^”):匹配指定范围外的****任意********单个****字符如果[]中有正则的元字符时,这些字符会失去特殊意义,被视作普通字符。但“-”、“^”、“\”除外,他们仍然有特殊意义。
\ (转移字符):- 转义:使后面的字符脱去原先的特殊含义,如:*
- 后面跟特殊字符实现特殊功能,如
\d:任何一个数字字符,等同于[0-9] \D:任何一个非数字字符,等同于[^0-9] \s:匹配任何不可见字符,相当于[\t\n\r\f\v] \S:匹配任何可见字符,相当于[^\t\n\r\f\v] \w:匹配包括下划线的任何单词字符。类似但不等价于“[A-Za-z0-9_]” \W:匹配任何非单词字符。等价于“[^A-Za-z0-9_]” \b:锚定词首,- 分组的后向引用 :\1,\2
*:匹配其前面的字符任意次。【例如】 .* :任意长度的任意字符?:匹配其前面的字符1次或0次(表示有或没有)+:≥1次(至少1次){m,n}:匹配其前面的字符至少m次,至多n次。【例如】{1,}{0,3}
默认情况下,以上四种*、?、+、{m,n}采用的是贪婪模式。如果要启用非贪婪模式,在匹配次数的元字符后加“?”,它表示成功匹配次数的最少次数。如\d*?
*?表示0次;??表示0次;+?表示1次;{m,n}?表示m次。
说明:在分析匹配模板时,请无视“?”的存在。
举个例子,贪婪模式解析:
源字符串:aa<div>test1</div>bb<div>test2</div>cc
正则表达式:<div>.*</div>
非贪婪模式下,在匹配到第一个“</div>”时已经可以使整个表达式匹配成功,结束匹配,匹配结果为“<div>test1</div>”。但是由于采用的是贪婪模式,所以仍然要向右尝试匹配,查看是否还有更长的可以成功匹配的子串,匹配到第二个“</div>”后,向右再没有可以成功匹配的子串,匹配结束,匹配结果为“<div>test1</div>bb<div>test2</div>”。
贪婪模式匹配的是第一个<div>和最后一个</div>,非贪婪模式匹配的是第一个<div>和第一个</div>。
^:锚定行首,此字符后面的任意内容必须出现在行首$:锚定行尾,此字符前面的任意内容必须出现在行尾^$:空白行\b:锚定单词边界\b:锚定词首,其后面的任意字符必须作为单词首部出现\b:锚定词尾,其前面的任意字符必须作为单词的尾部出现
单词:以字母组成,空格或标点符号隔开的字符串,不能有标点、数字等
|:或。例如(a|b)*,表示任意多个a或任意多个b( ):分组- 作用1:
(ab)*,表示ab作为一个整体被星号*修饰 - 作用2:后向引用,被括号括起来的内容后面可以用某个字符再次引用它
\1: 引用第一个左括号以及与之对应的右括号所包括的所有内容\2:\3:
- 作用1:
从首字母开始开始匹配,string如果包含pattern子串,则匹配成功,返回Match对象,失败则返回None。也就是说match()只有在0位置匹配成功的话才有返回,如果不是开始位置匹配成功的话,match()就返回none。
flags可以设置的值:
re.I(ignorecase)匹配时,不区分大小写re.M(multiline)可以同时在多行中匹配re.S(dotall)使点(.)可以匹配换行符\n.re.VERBOSE支持pattern中使用注释,但是此时会自动去掉pattern中的空格
若string中包含pattern子串,则返回Match对象(无论是否有分组),否则返回None 。注意,如果string中存在多个pattern子串,只返回第一个。
与re.match()方法的区别在于,re.search()不一定需要从首字母就要求匹配。
对于matech方法和search方法返回的Match对象,可以使用如下的方法:
group()或group(0):无论是否有分组,返回pattern匹配的整个子串。如果pattern中有分组,group(1)是与patttern中第一个group匹配成功的子串,group(2)是第二个,依次类推,如果index超了边界,抛出IndexError。
groups():如果pattern中没有分组,则返回空元组;如果pattern中有分组,返回一个以所有group为元素组成的元组,group(1)是与patttern中第一个group匹配成功的子串,group(2)是第二个,依次类推。
groupdict():
(?P<key>pattern)。如果pattern中没有分组,则返回空字典;如果pattern中有分组,返回一个以所有group为元素组成的字典。每个分组匹配的内容会赋值给key。
import re
ret=re.search('(?P<id>\d{3})/(?P<name>\w{3})','weeew34ttt123/ooo')
print(ret.group())
print(ret.group('id'))
print(ret.group('name'))
多次匹配,将匹配到所有的内容,放到一个列表里。类似re.finditer(),返回迭代器。
- findall()逐个字符去匹配,一旦匹配成功后的子串就不再参与后续的匹配,从匹配成功之后的字符开始继续匹配
r=re.findall('\d+\w\d+','a2b3c4d5')
print(r)
# ['2b3', '4d5']
- 如果pattern没有分组,返回的整个匹配的子串组成的列表;
r=re.findall('\wasd','1asd2asdp3asd98kif')
print(r)
# ['1asd', '2asd', '3asd']
- pattern有分组时,匹配过程:先以忽略分组信息的pattern去匹配,然后再对匹配到的字符串进行提取分组。此处分组的作用:从已经匹配到的子串中再提取分组(如果不想对某个分组进行提取,可以使用(?:pattern),此时这个分组里的内容就不再做提取操作,只是单纯的界定某个单元作用)。对于有分组的情况,findall返回的是提取的分组子串(分组对应的子串),而不是匹配的子串的整体。
如果整个pattern只有一个分组,返回匹配字符串组成的列表。
r=re.findall('(\wasd)','1asd2asdp3asd98kif') print(r) # ['1asd', '2asd', '3asd'] r=re.findall('\w(asd)','1asd2asdp3asd98kif') print(r) # ['asd', 'asd', 'asd']如果pattern中有多个分组,返回的是一个元组组成的列表。列表的元素是一个元组,元组的元素是pattern中的所有分组对应的子串。
r=re.findall('(\w)(asd)','1asd2asdp3asd98kif') print(r) # [('1', 'asd'), ('2', 'asd'), ('3', 'asd')]findall()分组嵌套
r=re.findall('(a)(\w+(e))(x)','hello alex bcd alex abcd alex lge acd 19') print(r) # [('a', 'le', 'e', 'x'), ('a', 'le', 'e', 'x'), ('a', 'le', 'e', 'x')]可以看出,分组的提取顺序:从左到右(同级),从外到内(嵌套)
如果pattern为
“”或“(xxx)*”或“(xxx)?”,空匹配也会出现在结果中。空匹配任何字符都会返回空,并且在字符串最末尾(\0)也会返回一个空。使用过程中应避免出现pattern整体为空的情况。r=re.findall('','alex') print(r) # ['', '', '', '', ''] 5个空 r=re.findall('(\w)(\w)(\w)(\w)','alex') print(r) # [('a', 'l', 'e', 'x')] r=re.findall('(\w){4}','alex') print(r) # ['x'] r=re.findall('(\w)*','alex') print(r) # ['x', '']分组重复,取一次匹配的最后一次分组,如下例中
1asd2asd取(\dasd)时,提取的是2asd。r=re.findall('(\dasd)*','1asd2asdp3asd98kif') print(r) # ['2asd', '', '3asd', '', '', '', '', '', ''] r=re.findall('(\dasd)*','1asd2asdpq3asd98kif') print(r) # ['2asd', '', '', '3asd', '', '', '', '', '', '']
以pattern匹配的子串来分割字符串,返回一个列表。
pattern没有分组,pattern匹配的子串直接分割。
r=re.split('a\w+','hello alex bcd alex shift abcd lge acd 19') print(r) # ['hello ', ' bcd ', ' shift ', ' lge ', ' 19']pattern中有分组,pattern匹配的子串直接分割,并且提取到的分组子串也会出现在列表
r=re.split('(a\w+)','hello alex bcd alex shift abcd lge acd 19') print(r) # ['hello ', 'alex', ' bcd ', 'alex', ' shift ', 'abcd', ' lge ', 'acd', ' 19']第三个参数maxsplit,来限制分割次数。
r=re.split('(a\w+)','hello alex bcd alex shift abcd lge acd 19',1) print(r) # ['hello ', 'alex', ' bcd alex shift abcd lge acd 19'] #特点:如果成功分割,列表有三个元素;否则,列表有一个元素 r=re.split('\(([^()]+)\)',"1-(3*(4+6)-6)/2") print(r) # ['1-(3*', '4+6', '-6)/2']
用repl子串来替换pattern匹配的子串,返回新的字符串。count是替换次数,默认为全部替换。
返回一个2个元素组成元组,元组内容为 (new_string, substitutions_number匹配次数)
可以使用序列解包(序列分解赋值)来接受返回值。如new_staring,count=re.subn()
ret=re.findall('(?:[^.0-9]|^)((?:(?:1[0-9][0-9]\.)|(?:2[0-4][0-9]\.)|(?:25[0-5]\.)|(?:[1-9][0-9]\.)|(?:[0-9]\.)){3}'
'(?:(?:1[0-9][0-9])|(?:2[0-4][0-9])|(?:25[0-5])|(?:[1-9][0-9])|(?:[0-9])))(?:[^.0-9]|$)','192.168.5.212AA 165.1.5.25AA565.1.5.21aa192.168.5.65.172.178.57.56 187.165.53.66')
print(ret)
([^.0-9]|$)为什么不写成([^.0-9$]),这是因为”^”和”$”不是普通字符,所以他们不会列入非[.0-9]的字符集和范围内,而我们又知道或(|)的两边应该是没有交集的两个集合.
r=re.findall('\bblow','blow')
print(r)
# []
r=re.findall('\\bblow','blow')
print(r)
# ['blow']
r=re.findall(r'\bblow','blow')
print(r)
# ['blow']
字符串前加”r”,表示字符串为raw字符串,字符串内形如”\n”,”\a”等字符就不再有特殊含义.而”\”在python中(ASCII码中)也有特殊意义。python拿到一个字符串模式首先会解析,将解析后的字符串传递给正则,正则再对python传递过来的字符串模式进行解析.例如re.findall('\\bblow','blow')中,python第一步解析后”\”会解析为”\”,所以整个字符串模式解析为”\bblow”,然后正则再来解析” \bblow”,将”\b”解析为锚定单词边界.
如果要让python把字符串不进行解析而直接传递给正则,此时就需要用到raw字符串.raw字符串会直接传递给正则,由正则直接来解析.建议如果匹配模式中含有”\x”的特殊含义的字符,直接传递raw字符给正则.