python Re模块学习

最近自己学习了正则表达式,自我感觉已经能够满足日常的使用了,需要去了解python提供的匹配正则表达式的工具包,学习参考官方文档,博客相当于笔记,毕竟人家官方文档才是最权威的。

一.写在前面

实现正则匹配需要正则表达式,也即Pattern,还需要待匹配的字符串,即String。两者可以用unicode编码的字符串来表示,也可以用8bit表示的字符串来表式,但是两者在本模块中不能混合使用,要么全使用unicode,要么全使用8bit。
在本模块使用中,正则表达式推荐用原始字符串来表示,即:

str1 = r'abc'  # 原始字符串要求字符串前面加‘r’

这样就不会受到python字符串本身的限制,如在python字符串中,表示一个反斜杠需要两个反斜杠来表示,即

str1 = '\\a'    # 才表示一个真正意义的斜杠
str2 = r'\a'
print(str1 == str2) #True

需要注意的是在Re模块中,实现同样的功能可以有两种方法,一种是使用模块级别的函数,一种是将正则表达式即Pattern编译成对象(使用对象的方法来完成),如下代码所示,在这里先不用纠结怎么实现的,体会一下有不同就行,后面还会讲到。
Re模块有两种对象,一种是正则表达式对象,一种是匹配对象。

import re 
regex = r'http(s)?:.*'
str1 = 'https://www.baidu.com/'
# 使用模块级别的函数
print(re.match(regex,str1).group()) # https://www.baidu.com/
# 使用编译对象的方式
pattern_obj = re.compile(regex)
print(pattern_obj.match(str1).group())
#https://www.baidu.com/

使用模块化的函数形式上比较简单,但会miss some fine-tuning parameters。第三方库regex也是一个实现正则匹配的库,对Re库完全兼容,但扩展了功能,也可以看看。

二.常用方法

1.re.compile(pattern, flags=0)

用于将正则表达式编译为正则表达式对象,在编译为对象中可以放入不同的标志组合,以设定不同的匹配模式。

import re 
pattern_obj = re.compile(r'http(s)?://[^\s]+',re.I|re.M)    
# re.I忽略大小写,re.M多行匹配,多个flag中间用‘|’连接

2.re.search()和pattern.search()

(1)re.search(pattern, string, flags=0)

re.search()属于模块化方法,
它会扫描整个字符串找到匹配上正则表达式的第一个位置,并返回一个匹配对象,如果没有找到就返回None

import re
match_obj1 = re.search(r'http(s)?://[^\s]+',r'https://www.baidu.com',re.I)
print(match_obj1.group()) # 'https://www.baidu.com'

match_obj2 = re.search(r'http(s)?://[^\s]+',r'HTTPS://www.baidu.com',re.I)
print(match_obj2.group()) # 'HTTPS://www.baidu.com'

match_obj3 = re.search(r'http(s)?://[^\s]+',r'HTTPS://www.baidu.com https://baidu.com',re.I)
print(match_obj3.group()) #'HTTPS://www.baidu.com'

从匹配对象1和2来看,忽略了大小写。从匹配对象1和3来看,search方法确实只找第一个匹配的字符串。
在这里简单提一下,设置正则匹配的模式(即flag)可以有三种方式:
是把表达式编译成对象时输入,如

pattern_obj = re.compile(r'http(s)?://[^\s]+',re.I|re.M) 

是使用模块化函数设定flag,如

match_obj = re.search(r'http(s)?://[^\s]+',r'HTTPS://www.baidu.com',re.I)

是在正则表达式中使用(?..)的扩展语法。这种扩展语法属于正则表达式本身的内容,跟这篇笔记的关系不是特别大,只举个例子,如:

match_obj = re.search(r'(?i)http(s)?://[^\s]+',r'HTTPS://www.baidu.com')
match_obj.group() # 'HTTPS://www.baidu.com'

(2)Pattern.search(string[, pos[, endpos]])

基本功能和模块级别函数的功能是一样的,不过使用对象的search方法可以指定搜索的开始和结束位置,这里有一点需要注意的地方就是,如果指定开始的位置pos>0,那么由于开始的位置不是string真正的开头,所以零宽断言‘^’就不会满足(不考虑多行匹配),如下代码:

import re
pattern_obj1 = re.compile(r'http(s)?://[^\s]+',re.I)
pattern_obj2 = re.compile(r'^http(s)?://[^\s]+',re.I)

print(pattern_obj1.search(r' https://www.baidu.com',pos=1).group())
# 输出'https://www.baidu.com
print(pattern_obj2.search(r' https://www.baidu.com',pos=1).group())
# 这语句会报错,因为search返回的match对象是None

3.re.match()和Pattern.match()

如果 string开始的0或者多个字符匹配到了正则表达式,就返回一个相应的 匹配对象 。 如果没有匹配,就返回 None
注意:即使在多行匹配模式下,match也只死盯着字符串的开头,而不是每行的开始

(1)re.match(pattern, string, flags=0)

import re
match_obj1 = re.match(r'http(s)?://[^\s]+','https://www.baidu.com')
match_obj2 = re.match(r'http(s)?://[^\s]+',' https://www.baidu.com')

print(match_obj1 == None) # False
print(match_obj2 == None) # True

(2)Pattern.match(string[, pos[, endpos]])

功能和模块级别方法一样,附加功能也跟上一条一样,不再赘述。

3.re.fullmatch()和Pattern.fullmatch()

如果整个 string 匹配到正则表达式,就返回一个相应的匹配对象 。 否则就返回一个 None

(1)re.fullmatch(pattern, string, flags=0)

import re
match_obj1 = re.fullmatch(r'http(s)?://[^\s]+','https://www.baidu.com')
match_obj2 = re.fullmatch(r'http(s)?://[^\s]+',' https://www.baidu.com')

print(match_obj1 == None) # False
print(match_obj2 == None) # True

(2)Pattern.fullmatch(string[, pos[, endpos]])

套路都一样,不再赘述。

4.re.split()和Pattern.split()

根据匹配到正则的序列来分开 string 。 如果在正则表达式中捕获到括号,那么所有的分隔符也会包含在分割后列表里。如果 maxsplit 非零, 最多进行 maxsplit 次分隔, 剩下的字符全部返回到列表的最后一个元素。

(1)re.split(pattern, string, maxsplit=0, flags=0)

一般来说,str.split()方法只能设定一种分隔符,所以re.split()方法用来设定多种分隔符比较方便。它的本质实际上还是进行正则匹配嘛,所以如果多个分隔符,要用字符集合’[’,’]'括起来。如:

import re
str1 = 'How are you,Mr Li.'
# 仅用空格来分割
str1_list1 = re.split(r'\s',str1) # ['How', 'are', 'you,Mr', 'Li.']
# 空格 逗号 和句号 作为分割
str1_list2 = re.split(r'[\s,.]',str1) # ['How', 'are', 'you', 'Mr', 'Li', '']
# 在正则中加入括号,来让分隔符也出现在list中
str1_list3 = re.split(r'([\s,.])',str1)
# ['How', ' ', 'are', ' ', 'you', ',', 'Mr', ' ', 'Li', '.', '']

在这里看官网的文档有点把我绕迷了,就是空匹配的问题,这里直接把官网例子拿出来说一下自己的理解

re.split(r'\b', 'Words, words, words.')
# ['', 'Words', ', ', 'words', ', ', 'words', '.']

re.split(r'\W*', '...words...')
# ['', '', 'w', 'o', 'r', 'd', 's', '', '']

re.split(r'(\W*)', '...words...')
# ['', '...', '', '', 'w', '', 'o', '', 'r', '', 'd', '', 's', '...', '', '', '']

对于第一条的解释:字边界的零宽断言在字的边界上匹配,匹配的地方位于第一个Word的’W’之前,‘s’和’,'之间,空格和第二个词之间,以此类推。

对于第二条的解释:r’\W*‘会产生空匹配,比如在w和o之间会产生无数空匹配,因此分割不发生在两个相邻空匹配的之间。在例子中,‘…’之前产生无数空匹配,‘…’产生一次有效匹配,所以用’…'分割一次;在‘…’和’w’之间产生无数次空匹配,但最左边的空匹配和前一个空匹配不相邻,故最左边的空匹配可以分割。故以此类推。其实相邻无数次空匹配被视为一个空匹配就好了。

(2)Pattern.split(string, maxsplit=0)

和re.split()完全等价,只不过编译后使用了。

5.re.findall()和Pattern.findall()

对 string 返回一个不重复的 pattern 的匹配列表, string 从左到右进行扫描,匹配按找到的顺序返回。如果样式里存在一到多个group,就返回一个group列表,形式上看是元组的列表(如果样式里有超过一个组合的话)。空匹配也会包含在结果里。

(1)re.findall(pattern, string, flags=0)

在实验findall时发现了一个小插曲,本来正则匹配默认整体是一个group的,但是如果正则表达式中有一个用()包起来的group时,它默认的group就变成了里面的group,外面的匹配结果就不显示了,如果要想显示整体的匹配就需要给外面整体加个括号。再或者就用非捕获组,(?:
)

import re
# 正则表达式中不添加括号做成group时
print(re.findall(r'(?i)https?://[^\s]+',"https://www.baidu.com HTTPS://WWW.BAIDU.COM"))
# 结果为:['https://www.baidu.com', 'HTTPS://WWW.BAIDU.COM']

# 正则表达式加一个括号做成group时
print(re.findall(r'(?i)http(s)?://[^\s]+',"https://www.baidu.com HTTPS://WWW.BAIDU.COM"))
# 结果为['s', 'S']

# 正则表达式加一个括号由于有上面的问题,给整体再加个括号后
print(re.findall(r'(?i)(http(s)?://[^\s]+)',"https://www.baidu.com HTTPS://WWW.BAIDU.COM"))
# 结果为[('https://www.baidu.com', 's'), ('HTTPS://WWW.BAIDU.COM', 'S')]

# 或者使用非捕获组
print(re.findall(r'(?i)http(?:s)?://[^\s]+',"https://www.baidu.com HTTPS://WWW.BAIDU.COM"))
# 结果为['https://www.baidu.com', 'HTTPS://WWW.BAIDU.COM']

(2)Pattern.findall(string[, pos[, endpos]])

跟search方法一样可以限制搜索的范围。

6.re.finditer()和Pattern.finditer()

作用和findall差不多,但是它会返回一个迭代对象,能够通过迭代对象得到每一个match对象。

(1)re.finditer(pattern, string, flags=0)

import re
m = re.finditer(r'(?i)http(s)?://[^\s]+',"https://www.baidu.com HTTPS://WWW.BAIDU.COM")
for match in m:
    print(match.group())
# https://www.baidu.com
# HTTPS://WWW.BAIDU.COM
# 由于正则表达式中有捕获组,可以给group方法里面输入1,就可以得到捕获组里面的东西

(2)Pattern.finditer(string[, pos[, endpos]])

可以限制搜索范围,不再赘述。

7.re.sub()和Pattern.sub()及扩展的 subn()

sub方法用于字符串内容的替换。

(1)re.sub(pattern, repl, string, count=0, flags=0)

repl是替代符号或者函数,将string中被pattern匹配到的东西按照repl进行替换。这个方法有点复杂。count是要替换的pattern最大出现次数,默认为0,即替换所有,返回替换后的字符串。subn()的方法返回一个元组,不仅包含替换后的字符串,也包含替换的次数
先说repl是字符或固定字符串的情况,这种最简单。

import re
print(re.sub(r'(blue|white|red)','color','blue socks and red shoes'))
# color socks and color shoes
print(re.subn(r'(blue|white|red)','color','blue socks and red shoes'))
# ('color socks and color shoes', 2)

再是repl是复杂字符串的情况。这种情况涉及到替换时的后向引用(官网这样叫的)

import re
print(re.sub(r'section\{([^}]*)\}',r'subsection{\1}','section{First} section{second}'))
# 'subsection{First} subsection{second}'
# 在repl中,\1表示后向引用,表明匹配到的第1组里面的东西重复出现在被替代的部分里面

类似的,除了\1这样用,若正则表达式中用了(?P<name>…),
则还可以用\g<name>和\g<number>来代替\1这样的用法,都是等价的,如下:

import re
print(re.sub(r'section\{([^}]*)\}',r'subsection{\g<1>}','section{First} section{second}'))
print(re.sub(r'section\{(?P<name>[^}]*)\}',r'subsection{\g<name>}','section{First} section{second}'))
# 输出均为 subsection{First} subsection{second}

repl是函数的情况:如果 repl 是一个函数,那它会对每个非重复的 pattern 的情况调用。这个函数只能有一个 匹配对象 参数,并返回一个替换后的字符串。

import re
def hexrepl(match):
    "Return the hex string for a decimal number"
    value = int(match.group())
    return hex(value)
str1 = re.sub(r'\d+',hexrepl, 'Call 65490 for printing, 49152 for user code.')
print(str1)
# Call 0xffd2 for printing, 0xc000 for user code.

(2)Pattern.sub(repl, string, count=0)

用法类似。

8.补充

模块级别的函数还有re.escape(pattern)和re.purge(),感觉不常用。
正则表达式对象除了以上的方法之外还具有一些属性。

(1)Pattern.flags

正则匹配标记。如:

import re
pattern_obj = re.compile(r'(?i)https?://[^\s]+')
print(pattern_obj.flags) # 34

(2)Pattern.groups

捕获到的模式串中组的数量.

import re
pattern_obj = re.compile(r'(?i)https?://[^\s]+')
print(pattern_obj.groups) # 0
pattern_obj = re.compile(r'(?i)http(s)?://[^\s]+')
print(pattern_obj.groups) # 1

(3)Pattern.groupindex

映射由 (?P) 定义的命名符号组合和数字组合的字典。如果没有符号组,那字典就是空的。

import re
pattern_obj = re.compile(r'(?P<file_name>\w+).(?P<extend_name>\w+)')
print(pattern_obj.groupindex) #{'file_name': 1, 'extend_name': 2}

意思就是说名为‘file_name’的组是第1组。

(4)Pattern.pattern

编译对象的原始样式字符串.

import re 
pattern_obj = re.compile(r'(?i)https?://[^\s]+')
print(pattern_obj.pattern) # (?i)https?://[^\s]+

三.匹配对象的方法和属性

match对象是在正则匹配时返回的对象,如re.search,re.match的返回值。

1.方法

(1)Match.expand(template)

该方法类似于re.sub方法,是将template中的一些地方用匹配到的信息进行替换。具体看例子:

import re 
match_obj = re.match(r'(?P<first_name>\w+)\s+(?P<last_name>\w+)','Heinrich Marx')

print(match_obj.expand(r'First name is \1,last name is \2'))
print(match_obj.expand(r'First name is \g<1>,last name is \g<2>'))
print(match_obj.expand(r'First name is \g<first_name>,last name is \g<last_name>'))
# 三者均输出 First name is Heinrich,last name is Marx
# 官网把这种引用叫做 数字引用 和命名组合

(2)Match.group([group1, …])

如果只有一个参数,结果就是一个字符串
如果有多个参数,结果就是一个元组(每个参数对应一个项)
如果没有参数,默认返回整个字符串的匹配结果(也即group 0 )

import re 
match_obj = re.match(r'(?P<first_name>\w+)\s+(?P<last_name>\w+)','Heinrich Marx')
print(match_obj.group()) # Heinrich Marx
print(match_obj.group(0)) # Heinrich Marx
print(match_obj.group(1)) # Heinrich
print(match_obj.group(2)) # Marx
print(match_obj.group(0,1,2)) # ('Heinrich Marx', 'Heinrich', 'Marx')

如果正则表达式使用了 (?P<name>…) 语法, groupN 参数就也可能是命名组合的名字,如m.group(‘first_name’)
这里有一点需要注意,如果一个组匹配成功多次,就只返回最后一个匹配

import re
m = re.match(r"(..)+", "a1b2c3")
print(m.group(1)) # c3

(3)Match.__getitem__(g)

这个等价于 m.group(g),官网例子就很好。

import re
 m = re.match(r"(\w+) (\w+)", "Isaac Newton, physicist")
 print(m[0])    # Isaac Newton 
 print(m[1])    # Isaac
 print(m[2])    # Newton

(4)Match.groups(default=None)

返回一个元组,包含所有匹配的子组,也就是在正则匹配中出现的从1到n的group。default是设定如果有的group没有匹配到的话默认返回什么。

import re
m = re.match(r"(\d+)\.(\d+)", "24.1632")
print(m.groups()) # ('24', '1632')
m = re.match(r"(\d+)\.?(\d+)?", "24")
print(m.groups('0')) # ('24', '0')

(5)Match.groupdict(default=None)

把子组匹配的结果以字典的形式返回,没匹配到的返回结果由default参数设定。

import re
m = re.match(r"(?P<first_name>\w+) (?P<last_name>\w+)", "Malcolm Reynolds")
print(m.groupdict()) # {'first_name': 'Malcolm', 'last_name': 'Reynolds'}

m = re.match(r"(?P<first_name>\w+) (?P<last_name>\w+)?", "Malcolm     ")
print(m.groupdict()) # {'first_name': 'Malcolm', 'last_name': None}

(6)Match.start([group])和Match.end([group])

返回 group 匹配到的字串的开始和结束标号。group 默认为0(就是整个匹配的字符串)。如果 group 存在,但未产生匹配,就返回 -1 。如果 group 匹配一个空字符串的话,m.start(group) 将会等于 m.end(group)

import re
m = re.match(r"(?P<first_name>\w+) (?P<last_name>\w+)?", "Malcolm     ")
# 匹配到的情况
print(m.start(1),m.end(1)) # 0 7
# 没有匹配到的情况
print(m.start(2),m.end(2)) # -1 -1

m = re.match(r"(?P<first_name>\w+) (?P<last_name>\w*)?", "Malcolm     ")
# 空匹配的情况
print(m.start(2),m.end(2)) # 8 8

注意和match.group()一样,在start()里面不止可以用数字,也可以用组名,如m.start(‘last_name’)

(7)Match.span([group])

返回一个二元组 (m.start(group), m.end(group)),如果 group 没有在这个匹配中,就返回 (-1, -1) 。group 默认为0,就是整个匹配.

2.属性

(1)Match.pos

这个值是由正则表达式对象传递的、指明从何处开始匹配。

import re
m = re.search(r"(?P<first_name>\w+) (?P<last_name>\w+)?", "  Malcolm     ")
print(m.pos) # 0
print(m.start()) # 2

还记得Pattern.search()方法中的pos参数嘛,m.pos就等于这个pos参数

(2)Match.endpos

跟Match.pos一样的套路

(3)Match.lastindex

捕获组的最后一个匹配的索引值,如果没有匹配产生的话就是None

import re
m = re.match(r"(?P<first_name>\w+) (?P<last_name>\w*)?", "Malcolm     ")
print(m.lastindex) # 2,这是因为产生了一个空匹配

m = re.match(r"(?P<first_name>\w+) (?P<last_name>\w+)?", "Malcolm     ")
print(m.lastindex) # 1,第二个毕竟没匹配上

(4)Match.lastgroup

最后一个匹配的组名字,如果没匹配上,就返回None

import re
m = re.match(r"(?P<first_name>\w+) (?P<last_name>\w+)?", "Malcolm     ")
print(m.lastgroup) # first_name

m = re.match(r"(\w+) (?P<last_name>\w+)?", "Malcolm     ")
print(m.lastgroup) # None
# 从这个可以看出来,如果没有给组命名的话,这个属性就不会记录。

(5)Match.re

返回产生这个实例的 正则对象,感觉用的也不太多。

(6)Match.string

返回传递到 match() 或 search() 的字符串。

import re
m = re.match(r"(?P<first_name>\w+) (?P<last_name>\w+)?", "Malcolm     ")
print(m.string) # 'Malcolm     '

结语

终于整完了,好累啊。虽然有很多例子是抄官网的,但是一个一个试也是好累,并且也加入了自己对这些方法或属性的探索。学习正则表达式和re模块最好还是要看官网的教程,优点是权威,全。缺点除了翻译很拗口,还有就是很难一次消化完。后面可能会写个博客总结一下正则表达式的内容。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值