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演示。