浅谈正则表达式的使用
By 青衣极客 Blue Geek In 2019-12-04
如果接到一个任务,需要分析程序运行的日志文件,并从其中提取相关信息做成报表,那该怎么实现呢?一种比较直观的方式是对字符串进行切分,然后根据关键字定位到信息所在子串。这种方式虽然也可行,并在很多的小项目中也确实可用,但不得不指出,这种方式的健壮性和效率都很低。因为,日志文件中的有些参量只是具有共同的特征,却并不是完全相同,而且那些不同的位置或许每次运行都不一样。这时,我们大概会考虑到正则表达式,因为它正是描述一种匹配模式的特征,具有通用型,并且编写的代码简洁,可读性更强,可维护性更好。正则表达式的所有语法自然是比较复杂的,但是在日常使用中,需要掌握的其实并不多,而且掌握了基本的使用方法之后,通过查看文档以扩展更加高阶的用法也更容易。本文就浅显讨论以下python中正则表达式的用法,运行环境为python3.7。
1. 模式Pattern
学习过“数据结构与算法”的同学对“模式”应该并不陌生,简要来说,模式就是目标字符串特征的描述。对于一个很长的“源字符串”,我们总是希望从中提取对我们有用的信息,这些有用的信息所在的子串就是我们的目标,而描述这些目标所具有的一致的特征就是模式。在Linux或者MacOS中常用grep指令的朋友对模式应该比较熟悉,只是在python中与grep有所不同,模式并不能简单由字符串表示,而必须使用专门的模块进行封装。python提供了re模块专门处理正则表达式相关的功能。下面展示以下python中模式的创建方式,也演示以下模式所具有的几个基本属性。
import re
p = re.compile(r'(hello).+?(world).+?(?P<vname>\d+)')
print('p={}'.format(p))
# 查看编译表达式时所用的字符串
print('p.pattern={}'.format(p.pattern))
# 查看该模式串的匹配模式
print('p.flags={}'.format(p.flags))
# 查看表达式中分组数量
print('p.groups={}'.format(p.groups))
#查看表达式中有别名的组
print('p.groupindex={}'.format(p.groupindex))
p=re.compile('(hello).+?(world).+?(?P<vname>\\d+)')
p.pattern=(hello).+?(world).+?(?P<vname>\d+)
p.flags=32
p.groups=3
p.groupindex={'vname': 3}
从以上演示可以看出,模式是编译“r”字符串得到的结果,其中使用“()”描述了多个需要匹配的子串。通过查看模式的属性可以发现,当前所定义的模式具有3个匹配项,也就是groups属性的值。其中第3个匹配项的名字为vname,正如groupindex属性所描述的。关于正则表达式的基本语法这里就不进行仔细描述,大家可以一边查看文档,一边在以上演示的基础上进行修改。
2. 匹配Match
生成模式之后,就可以使用这个模式在“愿字符串”中进行匹配。调用方式也非常简单,直接使用模式对象进行调用即可,当然,也可以使用“re”模块名进行调用,这里只演示更加直观清晰的前者。
m = p.match('hello nihao world shijie 2019hehehe')
print('m={}'.format(m))
print('m.string={}'.format(m.string))
print('m.re={}'.format(m.re))
print('m.group={}'.format(m.group()))
print('m.groups={}'.format(m.groups()))
print('m.groupdict={}'.format(m.groupdict()))
print('m.start={}'.format(m.start(3)))
print('m.end={}'.format(m.end(3)))
print('m.span={}'.format(m.span(3)))
m=<re.Match object; span=(0, 29), match='hello nihao world shijie 2019'>
m.string=hello nihao world shijie 2019hehehe
m.re=re.compile('(hello).+?(world).+?(?P<vname>\\d+)')
m.group=hello nihao world shijie 2019
m.groups=('hello', 'world', '2019')
m.groupdict={'vname': '2019'}
m.start=25
m.end=29
m.span=(25, 29)
从演示结果来看,该模式正确地从“源字符串”中分离出了我们所想要的三个匹配项。使用group函数可以查看匹配到的这三个匹配项的具体内容,使用groupdict函数可以查看那个带参数名的匹配项的具体内容,使用span函数可以查看每个匹配项子串在“源字符串”中的起始和结束位置。看到这里,那些使用字符串切分的方式提取日志信息的朋友,或许会感叹应该早点掌握正则表达式的用法。
3. 应用实例
在实际的开发中,处理上面演示的提取信息,还有一些情况下使用正则表达式也是可以处理的。这里列出4中应用实例,需要处理字符串的朋友可以对号入座,找到自己的应用场景直接开开始正则表达式的使用。
(1) 搜索子串
判断模式在“源字符串”中的存在性的时候,我们常常需要使用search方法,从以下的演示可以看出,search方法会匹配第一个符合“模式”的子串。
pp = re.compile(r'hello')
mm = pp.search('hello world, hello china')
print('mm.group={}'.format(mm.group()))
mm.group=hello
(2) 分割字符串
有时,我们还需要使用固定的模式来切分“源字符串”,python的“re”模块提供了split方法来完成这种需求。
pp = re.compile(r'hello')
slist = pp.split('hello world, hello china')
print('slist={}'.format(slist))
slist=['', ' world, ', ' china']
(3)查找所有子串
如果需要从“源字符串”中找到所有符合“模式”描述的子串,那么可以使用findall方法或者finditer方法。从函数名可以看出,后者是返回所有结果的迭代器,那么前者就是返回所有结果的子串列表了。
pp = re.compile(r'h\w+lo')
# 查找所有子串
s_all = pp.findall('hello world, hello china')
print('s_all={}'.format(s_all))
# 查找左右子串的迭代器
it = pp.finditer('hello world, hello china')
for s in it:
print('item={}'.format(s))
s_all=['hello', 'hello']
item=<re.Match object; span=(0, 5), match='hello'>
item=<re.Match object; span=(13, 18), match='hello'>
(4)替换子串
如果需要对“源字符串”中符合“模式”的一些匹配项进行替换,那么sub函数可以派上用场。只是更改匹配项之间的相对顺序,那么可以直接在sub方法调用时指定“r”字符串作为描述参数。
p = re.compile(r'(hello).+?(world)')
print('p.sub={}'.format(p.sub(r'\2 \1', 'hello world, hello china')))
p.sub=world hello, hello china
如果需要对子串进行一些修改再替换,那么可以使用函数封装一次匹配的返回值。
def func(m):
return 'nihao shijie'
print('p.sub={}'.format(p.sub(func, 'hello world, hello china')))
p.sub=nihao shijie, hello china
到此,关于python中使用正则表达式的简要讨论就结束了。学会使用正则表达式不仅能够显著提高字符串处理的开发效率,而且也有利于程序处理的鲁棒性、通用型和可维护性。既然如此有利,而且可以偷懒,那么作为一个以“懒惰即美德”为追求的程序开发者而言,何乐而不为?

COMMENT
博客评论区功能由Github Issue提供,提交Issue时请以本文标题为话题。
"BG52-浅谈正则表达式的使用"