python用re.sub实现分组匹配和替换(及问答系统中的应用)

  • 2019 年 10 月 30 日
  • 笔记

关于正则表达式替换,前面我写过一个应用: python2代码搬运到python3要改很多print? 试试用pyCharm的正则表达式替换

其实这里的替换已经使用了分组的思想。 上面一行的匹配模式print (S*)中,括号括起的部分匹配到的内容就被识别为匹配组1。而下一行的替换模式中,$1就指代了匹配组1的内容。 所以在这个例子里,匹配组1匹配到的内容是“123”,而在替换时,“123”就替换了$1对应的位置。

有时候,我们可能需要从一句话中提取多个分组,并且替换其中的全部,或者仅仅是部分几组。这个问题同样可以用正则表达式解决。这个方法是我在研究问答系统时琢磨出来的,所以我也以此作为例子:

现在,我们的问答系统需要回答这样一个问题:

曹丕的父亲是谁?

回答这个问题,要求我们把其中的“曹丕”和“父亲”提取出来(有时候也可以提取“谁”,用于限定答案的范围必须是一个人),然后就可以利用这两个条件在知识库中查找答案。 这样,这个问题就转化为用正则表达式提取其中的三个分组。下面是我为此写的一个正则表达式:

import re  quest = "曹丕的父亲是谁?"  template = re.compile(r"(S[^的]*)的(S[^是]*)是(S[^?]*)?")  matches = re.search(template,quest)  if matches:      print(matches.group(0))                   # full match      print(matches.group(1))                   # match group1      print(matches.group(2))                   # match group2      print(matches.group(3))                   # match group3

结果

曹丕的父亲是谁? 曹丕 父亲 谁

而在回答时,我希望能够保持原句的语法,比如:

>>> answer("曹丕的父亲是谁?")  曹丕的父亲是曹操

这就意味着我们需要保留前两个分组,而把第三个分组用查找到的答案替换掉,假设已经查到答案,方法如下:

ans = "曹操"  re.sub(template,r"1的2是%s" % ans,quest)

曹丕的父亲是曹操

其中的1,2就表示第1、第2匹配组的内容(“曹丕”、“父亲”)。 问题词可以出现在不同位置,不过经过调整以后依然可以用正则表达式解决这问题,效果比如:

>>> answer("谁的父亲是曹操?")  曹彰的父亲是曹操  曹丕的父亲是曹操  曹植的父亲是曹操  曹昂的父亲是曹操

这是我实现的一个极简的基于知识库的问答系统的一部分,如果对其中的实现细节(包括正则表达式的适应性调整、知识图谱的查询SPARQL)感兴趣,可以在这里看到我更详细的jupyter notebook演示。