正则表达式中零宽断言的用法

了解了正则表达式,想必通常状况下的匹配都不会出现什么问题,可是若是一些特殊状况,可能须要用到一些更高级的正则表达式匹配操做,本节咱们来讲明一下正则表达式的一个较经常使用又比较重要的知识点——零宽断言。
正则表达式

实例引入

首先咱们来看一个例子,这里有一段问答对话:浏览器

问:我用的是Windows XP+Service Pack 2,为何没法安装输入卡号和密码的控件? 答:在Windows XP+Service Pack 二、Windows 2003等操做系统中,用户能够本身选择是否安装控件。 问:为何我看到的卡号输入框显示为*符号? 答:您的浏览器禁止下载执行ActiveX控件 , 对于这种状况 , 您必须打开浏览器的ActiveX的相关权限。 操做方法:在浏览器菜单中选择“工具”|“Internet选项”,在弹出的对话框中选择"安全" |"Internet"|"自定义级别",在弹出的对话框中选择"重置为 安全级-中" , 点"重置"按钮,肯定。 问:看了以上几个问题,仍是不能登陆,怎么办? 答:您的浏览器因为其余缘由不能安装招商银行登陆控件, 请下载并安装招商银行登陆控件下载版。 问:没法出现我的网上银行大众版登陆界面。 答:这种状况是因为您的机器没法和我行服务器创建安全链接,一般是由于代理服务器设置错误引发。若是您是拨号上网,请不要使用代理服务器;若是您过去安装过我行SSL安全代理,请调用“添加-删除程序”删除SSL安全代理;若是您是通过代理访问Internet,请联系您所在网的网络管理员设置代理服务器。IE5.0浏览器设置代理服务器的步骤: Internet选项-->链接-->局域网设置-->使用代理服务器-->高级。 问:我在输入帐号和卡号时,总出错,该怎样输? 答:存折帐号为10位,按存折本上的帐号输入, 密码为6位。若是一卡通是12位卡号的,只需输入地区码后面的8位卡号,不须要输入前面4位的地区码,密码为6位。若是一卡通是16位卡号的,请将16位卡号所有输入,密码为6位。 问:个人存折没有设密码,怎样在我的网上银行大众版中查询余额? 答:存折必须设有密码方可在 我的网上银行大众版 中查询,所以请您到存折开户行给您的存折设置密码。 注:网上我的银行是招商银行为我的客户提供的网上银行。 本页面内容仅供参考,部分业务以当地网点的公告与具体规定为准。安全

咱们须要将这段对话中的问题和答案对提取出来,即提取出以下内容:服务器

Q:我用的是Windows XP+Service Pack 2,为何没法安装输入卡号和密码的控件?网络

A:在Windows XP+Service Pack 二、Windows 2003等操做系统中,用户能够本身选择是否安装控件。app

Q:为何我看到的卡号输入框显示为*符号?ide

A:您的浏览器禁止下载执行ActiveX控件 , 对于这种状况 , 您必须打开浏览器的ActiveX的相关权限。 操做方法:在浏览器菜单中选择“工具”|“Internet选项”,在弹出的对话框中选择"安全" |"Internet"|"自定义级别",在弹出的对话框中选择"重置为 安全级-中" , 点"重置"按钮,肯定。工具

...ui

若是要用 Python 实现的话,那么咱们极可能天然而然想到 split() 或 findall() 方法,若是用 split() 方法,咱们可能会这么写:spa

import re
results = re.split('问:| 答:', text)
for index, result in enumerate(results[1:]):    print(('Q' if index%2 == 0 else 'A') + ': ' + result)

这里 split() 方法的第一个参数传入了 `问:| 答:` 这个正则表达式,意思是将这段话用 `问:` 或者 `答:` 分开,这个功能是正则表达式对字符串进行分割的方法,相比直接字符串的 split() 方法功能更为强大。这里其实获得的结果是一个列表,长度是一个奇数,若是咱们把 results 打印出来,结果是这样的:

['', '我用的是Windows XP+Service Pack 2,为何没法安装输入卡号和密码的控件?', '在Windows XP+Service Pack 二、Windows 2003等操做系统中,用户能够本身选择是否安装控件。 ', '为何我看到的卡号输入框显示为*符号?', '您的浏览器禁止下载执行ActiveX控件 , 对于这种状况 , 您必须打开浏览器的ActiveX的相关权限。 操做方法:在浏览器菜单中选择“工具”|“Internet选项”,在弹出的对话框中选择"安全" |"Internet"|"自定义级别",在弹出的对话框中选择"重置为 安全级-中" , 点"重置"按钮,肯定。 ', '看了以上几个问题,仍是不能登陆,怎么办?', '您的浏览器因为其余缘由不能安装招商银行登陆控件, 请下载并安装招商银行登陆控件下载版。 ', '没法出现我的网上银行大众版登陆界面。', '这种状况是因为您的机器没法和我行服务器创建安全链接,一般是由于代理服务器设置错误引发。若是您是拨号上网,请不要使用代理服务器;若是您过去安装过我行SSL安全代理,请调用“添加-删除程序”删除SSL安全代理;若是您是通过代理访问Internet,请联系您所在网的网络管理员设置代理服务器。IE5.0浏览器设置代理服务器的步骤: Internet选项-->链接-->局域网设置-->使用代理服务器-->高级。 ', '我在输入帐号和卡号时,总出错,该怎样输?', '存折帐号为10位,按存折本上的帐号输入, 密码为6位。若是一卡通是12位卡号的,只需输入地区码后面的8位卡号,不须要输入前面4位的地区码,密码为6位。若是一卡通是16位卡号的,请将16位卡号所有输入,密码为6位。 ', '个人存折没有设密码,怎样在我的网上银行大众版中查询余额?', '存折必须设有密码方可在 我的网上银行大众版 中查询,所以请您到存折开户行给您的存折设置密码。 注:网上我的银行是招商银行为我的客户提供的网上银行。 本页面内容仅供参考,部分业务以当地网点的公告与具体规定为准。 ']

这是由于咱们分割使用的字符自己就处于整个文本的字符,因此一上来就找到了分割的标志 `问:`,因此它左侧的结果就是空字符串了,因此最终获得的结果第一个内容就是空字符串,后续的内容即是正常的一问一答的短句。因此这里咱们还须要对结果进行切片操做,去除第一个元素,而后将其遍历打印输出,最终结果以下:

Q: 我用的是Windows XP+Service Pack 2,为何没法安装输入卡号和密码的控件?
A: 在Windows XP+Service Pack 二、Windows 2003等操做系统中,用户能够本身选择是否安装控件。 
Q: 为何我看到的卡号输入框显示为*符号?
A: 您的浏览器禁止下载执行ActiveX控件 , 对于这种状况 , 您必须打开浏览器的ActiveX的相关权限。 操做方法:在浏览器菜单中选择“工具”|“Internet选项”,在弹出的对话框中选择"安全" |"Internet"|"自定义级别",在弹出的对话框中选择"重置为 安全级-中" , 点"重置"按钮,肯定。 
Q: 看了以上几个问题,仍是不能登陆,怎么办?
A: 您的浏览器因为其余缘由不能安装招商银行登陆控件, 请下载并安装招商银行登陆控件下载版。 
Q: 没法出现我的网上银行大众版登陆界面。
A: 这种状况是因为您的机器没法和我行服务器创建安全链接,一般是由于代理服务器设置错误引发。若是您是拨号上网,请不要使用代理服务器;若是您过去安装过我行SSL安全代理,请调用“添加-删除程序”删除SSL安全代理;若是您是通过代理访问Internet,请联系您所在网的网络管理员设置代理服务器。IE5.0浏览器设置代理服务器的步骤: Internet选项-->链接-->局域网设置-->使用代理服务器-->高级。 
Q: 我在输入帐号和卡号时,总出错,该怎样输?
A: 存折帐号为10位,按存折本上的帐号输入, 密码为6位。若是一卡通是12位卡号的,只需输入地区码后面的8位卡号,不须要输入前面4位的地区码,密码为6位。若是一卡通是16位卡号的,请将16位卡号所有输入,密码为6位。 
Q: 个人存折没有设密码,怎样在我的网上银行大众版中查询余额?
A: 存折必须设有密码方可在 我的网上银行大众版 中查询,所以请您到存折开户行给您的存折设置密码。 注:网上我的银行是招商银行为我的客户提供的网上银行。 本页面内容仅供参考,部分业务以当地网点的公告与具体规定为准。

这样确实没问题,咱们能够顺利地提取出来,可是总感受这个解法并不那么优雅,由于咱们这里是将问题和答案的内容都单独切出来了,并无将问答对一块提取,并且 split() 方法返回的结果的第一个元素还不是咱们想要的结果,因此还须要进行一些切片操做来去除,因此整个写法感受实现起来并不完美。

因此咱们又想到了 findall() 方法,这时咱们会这么写:

import re
results = re.findall('问:(.*?) 答:(.*?)', text, re.S)
for result in results:    print('Q: ' + result[0], 'A: ' + result[1], sep='\n')

表面上看彷佛是把问题答案对用正则表示出来了,并且使用了非贪婪匹配,可是很明显,在末尾咱们并无指定匹配的终点,因此整个的结果就会致使回答是彻底匹配不到的,运行结果以下:

Q: 我用的是Windows XP+Service Pack 2,为何没法安装输入卡号和密码的控件?
A: 
Q: 为何我看到的卡号输入框显示为*符号?
A: 
Q: 看了以上几个问题,仍是不能登陆,怎么办?
A: 
Q: 没法出现我的网上银行大众版登陆界面。
A: 
Q: 我在输入帐号和卡号时,总出错,该怎样输?
A: 
Q: 个人存折没有设密码,怎样在我的网上银行大众版中查询余额?
A:

好,那么咱们加上匹配的终点吧,如下一个的 `问:` 做为咱们正则表达式匹配的终点总能够了吧?因此咱们可能会改写成这样子:

import re
results = re.findall('问:(.*?) 答:(.*?)问:', text, re.S)
for result in results:    print('Q: ' + result[0], 'A: ' + result[1], sep='\n')

这样写彷佛看起来是能够了,但结果倒是这样的:

Q: 我用的是Windows XP+Service Pack 2,为何没法安装输入卡号和密码的控件?
A: 在Windows XP+Service Pack 二、Windows 2003等操做系统中,用户能够本身选择是否安装控件。 
Q: 看了以上几个问题,仍是不能登陆,怎么办?
A: 您的浏览器因为其余缘由不能安装招商银行登陆控件, 请下载并安装招商银行登陆控件下载版。 
Q: 我在输入帐号和卡号时,总出错,该怎样输?
A: 存折帐号为10位,按存折本上的帐号输入, 密码为6位。若是一卡通是12位卡号的,只需输入地区码后面的8位卡号,不须要输入前面4位的地区码,密码为6位。若是一卡通是16位卡号的,请将16位卡号所有输入,密码为6位。

结果只剩三个问题答案对了,有三个问答对被“吃”掉了,其实这是由于咱们的正则表达式最后加了 问:的缘故,findall() 方法它会查找全部符合正则表达式的结果,但其中匹配的时候它内部也是有一个查找索引在扫描的。在查找第一个符合要求的结果时,因为咱们是根据正则表达式结尾的 问:来做为结束标志,因此在找到第一个符合要求的结果时,咱们的查找索引就已经移动到了第二个问答对开头的 问: 上面,即查找索引就已经进入到了第二个问答对的位置了,而在下一次查找符合要求的结果时,索引会继续日后移动进行扫描,因此它是从第二个问答对的 问: 后面继续扫描的,因此对于第二个问答对,实际上已经被割裂了,因此它只能查找到第三个问答对的时候才能够发现符合正则表达式的内容。所以,咱们能够观察到,返回的结果只是第1、3、五三个问答对。

因此,若是咱们想要用该方法找到完整的留个问答对,就须要用到零宽断言了。

解法以下:

import re
results = re.findall('问:(.*?) 答:(.*?)(?=问:|\Z)', text, re.S)
for result in results:    print('Q: ' + result[0], 'A: ' + result[1], sep='\n')

运行结果以下:

Q: 我用的是Windows XP+Service Pack 2,为何没法安装输入卡号和密码的控件?
A: 在Windows XP+Service Pack 二、Windows 2003等操做系统中,用户能够本身选择是否安装控件。 
Q: 为何我看到的卡号输入框显示为*符号?
A: 您的浏览器禁止下载执行ActiveX控件 , 对于这种状况 , 您必须打开浏览器的ActiveX的相关权限。 操做方法:在浏览器菜单中选择“工具”|“Internet选项”,在弹出的对话框中选择"安全" |"Internet"|"自定义级别",在弹出的对话框中选择"重置为 安全级-中" , 点"重置"按钮,肯定。 
Q: 看了以上几个问题,仍是不能登陆,怎么办?
A: 您的浏览器因为其余缘由不能安装招商银行登陆控件, 请下载并安装招商银行登陆控件下载版。 
Q: 没法出现我的网上银行大众版登陆界面。
A: 这种状况是因为您的机器没法和我行服务器创建安全链接,一般是由于代理服务器设置错误引发。若是您是拨号上网,请不要使用代理服务器;若是您过去安装过我行SSL安全代理,请调用“添加-删除程序”删除SSL安全代理;若是您是通过代理访问Internet,请联系您所在网的网络管理员设置代理服务器。IE5.0浏览器设置代理服务器的步骤: Internet选项-->链接-->局域网设置-->使用代理服务器-->高级。 
Q: 我在输入帐号和卡号时,总出错,该怎样输?
A: 存折帐号为10位,按存折本上的帐号输入, 密码为6位。若是一卡通是12位卡号的,只需输入地区码后面的8位卡号,不须要输入前面4位的地区码,密码为6位。若是一卡通是16位卡号的,请将16位卡号所有输入,密码为6位。 
Q: 个人存折没有设密码,怎样在我的网上银行大众版中查询余额?
A: 存折必须设有密码方可在 我的网上银行大众版 中查询,所以请您到存折开户行给您的存折设置密码。 注:网上我的银行是招商银行为我的客户提供的网上银行。 本页面内容仅供参考,部分业务以当地网点的公告与具体规定为准。

这里咱们其实是使用了 (?=)这样的形式来构建了整个表达式,等号后面的内容是 问:或者结束符 \Z,这样其实就保证了在匹配的时候,查找索引不会继续向后移,但这也同时标志告终束标志,所以它就能够查找到完整的内容了。

零宽断言

零宽断言,顾名思义,是一种零宽度的匹配,它匹配的内容不会保存到匹配结果中,表达式的匹配内容只是表明了一个位置而已,如标明某个字符的右边界是怎样的构造。

在前面咱们使用了 ?=来进行了实例讲解,这是其中一个用法,另外还有 ?<=?!?<!,下面咱们来依次进行讲解说明。

  • ?=表明零宽度正预测先行断言,它断言自身出现的位置的后面能够匹配后面跟的表达式。

  • ?<=表明零宽度正回顾后发断言,它断言自身出现的位置的前面能够匹配后面跟的表达式。

  • ?!表明零宽度负预测先行断言,它断言自身出现的位置的后面不能够匹配后面跟的表达式。

  • ?<!表明零宽度负回顾后发断言,它断言自身出现的位置的后面不能够匹配后面跟的表达式。

?=

首先咱们来看下 ?=的用法,它断言自身出现的位置的后面能够匹配后面跟的表达式。

好比咱们这里有这样的一个字符串:

str = '个人我的邮箱是cqc@cuiqingcai.com,我的博客是cuiqingcai.com,我的公众号是进击的Coder'

在这里咱们想把个人我的邮箱这句话和我的邮箱单独摘出来,假如咱们不使用零宽断言的话,咱们须要给我的邮箱后面这一句加一个结束标识符或者单独匹配邮箱做为标识符,咱们可能会这么写:

import re
str = '个人我的邮箱是cqc@cuiqingcai.com,我的博客是cuiqingcai.com,我的公众号是进击的Coder'
result = re.search('个人我的邮箱是(.*?),我的博客', str) print('整句结果:' + result.group(), '第一个匹配结果:' + result.group(1), sep='\n')

在正则表达式的最后咱们加了,我的博客做为匹配的结束符,而后邮箱部分用非贪婪匹配的模式进行匹配,咱们看下运行结果:

整句结果:个人我的邮箱是cqc@cuiqingcai.com,我的博客
第一个匹配结果:cqc@cuiqingcai.com

咱们能够看到第一个匹配结果成功获得了邮箱信息,可是咱们看整句结果缺并不理想,它多匹配了咱们加入的结尾标识,并无获得正常的一句话。

这时候若是咱们改用 ?=来匹配,结果就不会带有此标识符了,改写以下:

import re
str = '个人我的邮箱是cqc@cuiqingcai.com,我的博客是cuiqingcai.com,我的公众号是进击的Coder'
result = re.search('个人我的邮箱是(.*?)(?=,我的博客)', str) print('整句结果:' + result.group(), '第一个匹配结果:' + result.group(1), sep='\n')

在这里咱们将结尾标识符改为了 (?=,我的博客) ,这样就将此部份内容做为零宽度匹配,它表明后面须要跟 ,我的博客,可是它不会出如今匹配结果中。

运行结果以下:

整句结果:个人我的邮箱是cqc@cuiqingcai.com
第一个匹配结果:cqc@cuiqingcai.com

能够看到整句结果中已经没有无用的后缀字符了。

?<=

接下来咱们再看下 ?<=的用法,它表明零宽度正回顾后发断言,其实就是匹配前面的标识,好比这里咱们仍是以上面的例子为例,匹配出我的博客这句话,代码以下:

import re
str = '个人我的邮箱是cqc@cuiqingcai.com,我的博客是cuiqingcai.com,我的公众号是进击的Coder'
result = re.search('(?<=,)我的博客是(.*?)(?=,)', str) print('整句结果:' + result.group(), '第一个匹配结果:' + result.group(1), sep='\n')

这里咱们在我的博客 前面加了一个零宽断言的逗号符号做为开头,使用的就是 ?<=,句子结尾是用的 ?=,这样先后的标识都不会匹配到了,运行结果以下:

整句结果:我的博客是cuiqingcai.com
第一个匹配结果:cuiqingcai.com

能够看到获得的整句结果也是完整的一句话。

?!

?!表明表明零宽度负预测先行断言,它断言自身出现的位置的后面不能够匹配后面跟的表达式。也是用来匹配后面的文本,但这里是取反,它指定了后面出现的内容不匹配该标识,咱们在前面的例子基础上修改以下:

import re
str = '个人我的邮箱是cqc@cuiqingcai.com,我的博客是cuiqingcai.com,我的公众号是进击的Coder'
result = re.search('个人我的邮箱是(.*?)(?!,我的公众号)(?=,我的博客)', str) print('整句结果:' + result.group(), '第一个匹配结果:' + result.group(1), sep='\n')

原本是 (?=,我的博客)的标识符,不过这里咱们使用 ?!来指定了另外一个标识符,我的公众号,这就表明这句话后面跟的须要是(?=,我的博客)而不是,我的公众号,运行结果以下:

整句结果:个人我的邮箱是cqc@cuiqingcai.com
第一个匹配结果:cqc@cuiqingcai.com

?<!

?<!表明零宽度负回顾后发断言,它断言自身出现的位置的后面不能够匹配后面跟的表达式。咱们在前面的例子基础上加以修改:

import re
str = '个人我的邮箱是cqc@cuiqingcai.com,我的博客是cuiqingcai.com,我的公众号是进击的Coder'
result = re.search('(?<=,)(?<!。)我的博客是(.*?)(?=,)', str) print('整句结果:' + result.group(), '第一个匹配结果:' + result.group(1), sep='\n')

这里咱们写了 ?<!标识符,后面跟了一个句号,这表明前面不该该出现句号。

运行结果以下:

整句结果:我的博客是cuiqingcai.com
第一个匹配结果:cuiqingcai.com

经常使用用法

其实上面的示例中咱们使用了 search() 方法进行了内容匹配,其实这并不经常使用,由于通常咱们更关注的是匹配分组结果的内容,其实更多的用法是用在了 findall() 方法上,它用来匹配多个结果,也就相似于咱们一开始的实例同样,这里咱们仍是以刚才的字符串为例,来输出一下我的邮箱、我的博客、我的公众号三个内容,代码以下:

import re
str = '个人我的邮箱是cqc@cuiqingcai.com,我的博客是cuiqingcai.com,我的公众号是进击的Coder'
results = re.findall('我的(.*?)是(.*?)(?=,|\Z)', str)
for result in results:    print(result[0] + ': ' + result[1])

这里咱们匹配了我的二字,而后后面跟了非贪婪匹配,而后加了一个字,最关键的是结尾标识符,这里必需要使用零宽断言才能够匹配出三个结果,这里匹配的内容是 ,|\Z,意思是匹配逗号或结束符。

运行结果以下:

邮箱: cqc@cuiqingcai.com
博客: cuiqingcai.com 公众号: 进击的Coder

这样咱们就成功输出了邮箱、博客及公众号的内容了,匹配很是顺利方便。

结语

经过本节,咱们应该大致能够了解了正则表达式中零宽断言的基本用法和适用场景,相信理解了零宽断言以后,咱们再作正则匹配时会更加驾轻就熟。

相关文章
相关标签/搜索