本文主要总结了python正则零宽断言(zero-length-assertion)的一些常用用法。

1. 什么是零宽断言

有时候在使用正则表达式做匹配的时候,我们希望匹配一个字符串,这个字符串的前面或后面需要是特定的内容,但我们又不想要前面或后面的这个特定的内容,这时候就需要零宽断言的帮助了。所谓零宽断言,简单来说就是匹配一个位置,这个位置满足某个正则,但是不纳入匹配结果的,所以叫“零宽”,而且这个位置的前面或后面需要满足某种正则。

比如对于一个字符串:"finished going done doing",我们希望匹配出其中的以ing结尾的单词,就可以使用零宽断言:

import re
s = 'finished going done doing'
p = re.compile(r'\b\w+(?=ing\b)') print '【Output】'
print [x + 'ing' for x in re.findall(p,s)]
【Output】
['going', 'doing']

可以看出从中匹配出了'going'和'doing‘两个单词,达到目的。

这里正则中使用的(?=ing\b)就是一种零宽断言,它匹配这样一个位置:这个位置有一个'ing'字符串,后面跟着一个'\b'符号,并且这个位置前面的字符串满足正则:\b\w+,于是匹配结果就是:['go','do']

2. 不同的零宽断言

零宽断言分为四种:正预测先行断言、正回顾后发断言、负预测先行断言、负回顾后发断言,不同的断言匹配的位置不同。

总结一下,这几个仿佛说的不是"人话"的令人费解的名词可以这样理解:其中的“正”指的是肯定预测,即某个位置满足某个正则,而与之对应的“负”则指的是否定预测,即某个位置不要满足某个正则;其中的“预测先行”则指的是“往后看”,“先往后走”的意思,即这个位置是出现在某一个字符串后面的,而与之相反的“回顾后发”则指的是相反的意思:“往前看”,即匹配的这个位置是出现在某个字符串的前面的。

不理解没关系,我们用实例说话,下面对每种零宽断言进行详细介绍。

1. 正预测先行断言:(?=exp)

匹配一个位置(但结果不包含此位置)之前的文本内容,这个位置满足正则exp,举例:匹配出字符串s中以ing结尾的单词的前半部分:

s = "I'm singing while you're dancing."
p = re.compile(r'\b\w+(?=ing\b)') print '【Output】'
print re.findall(p,s)
【Output】
['sing', 'danc']

2. 正回顾后发断言:(?<=exp)

匹配一个位置(但结果不包含此位置)之后的文本,这个位置满足正则exp,举例:匹配出字符串s中以do开头的单词的后半部分:

s = "doing done do todo"
p = re.compile(r'(?<=\bdo)\w+\b') print '【Output】'
print re.findall(p,s)
【Output】
['ing', 'ne']

3. 负预测先行断言:(?!exp)

匹配一个位置(但结果不包含此位置)之前的文本,此位置不能满足正则exp,举例:匹配出字符串s中不以ing结尾的单词的前半部分:

s = 'done run going'
p = re.compile(r'\b\w+(?!ing\b)') print '【Output】'
print re.findall(p,s)
【Output】
['done', 'run', 'going']

可见,出问题了,这不是我们预期的结果(预期的结果是:done和run),这是因为负向断言不支持匹配不定长的表达式,将p改一下再匹配:

s = 'done run going'
p = re.compile(r'\b\w{2}(?!ing\b)') print '【Output】'
print re.findall(p,s)
【Output】
['do', 'ru']

可见一次只能匹配出固定长度的不以ing结尾的单词,没有完全达到预期。这个问题还有待解决。

4. 负回顾后发断言:(?<!exp)

匹配一个位置(但结果不包含此位置)之后的文本,这个位置不能满足正则exp,举例:匹配字符串s中不以do开头的单词:

s = 'done run going'
p = re.compile(r'(?<!\bdo)\w+\b') print '【Output】'
print re.findall(p,s)
【Output】
['done', 'run', 'going']

可见也存在与负预测先行断言相同的问题,改一下:

s = 'done run going'
p = re.compile(r'(?<!\bdo)\w{2}\b') print '【Output】'
print re.findall(p,s)
【Output】
['un', 'ng']

5. 正向零宽断言的结合使用

举例:字符串ip是一个ip地址,现在要匹配出其中的四个整数:

ip = '160.158.0.77'
p = re.compile(r'(?<=\.)?\d+(?=\.)?') print '【Output】'
print re.findall(p,ip)
【Output】
['160', '158', '0', '77']

6. 负向零宽断言的结合使用

举例:匹配字符串s中的一些单词,这些单词不以'x'开头且不以'y'结尾:

s = 'xaay xbbc accd'
p = re.compile(r'(?<!\bx)\w+(?!y\b)') print '【Output】'
print re.findall(p,s)
【Output】
['xaay', 'xbbc', 'accd']

可见这里因为负向断言不支持不定长表达式,所以也存在和前面相同的问题。

3. 零宽断言的应用

1. 匹配html标签之间的内容

s = '<span>Hello world!</span>'
p = re.compile(r'(?<=<(?:\w+)>(.*)(?=</\1>))') print '【Output】'
print re.findall(p,s)
# 报错:error: look-behind requires fixed-width pattern

上面的报错是因为零宽断言的正则中不能含有不定长的表达式,改一下:

s = '<span>Hello world!</span>'
p = re.compile(r'(?<=<(\w{4})>)(.*)(?=</\1>)') print '【Output】'
print re.findall(p,s)
【Output】
[('span', 'Hello world!')]

2. 匹配存在多种规则约束(含否定规则)的字符串

匹配一个长度为4个字符的字符串,该字符串只能由数字、字母或下划线3种字符组成,且必须包含其中的至少两种字符,且不能以下划线或数字开头:

# 测试数据
strs = ['_aaa','1aaa','aaaa','a_12','a1','a_123','1234','____']
p = re.compile(r'^(?!_)(?!\d)(?!\d+$)(?![a-zA-Z]+$)\w{4}$') print '【Output】'
for s in strs:
print re.findall(p,s)
【Output】
[]
[]
[]
['a_12']
[]
[]
[]
[]

3. 注意点

零宽断言虽然也是用小括号括起来的,但不占用分组的默认命名空间。举例如下:

s = 'goingxxx'
# 在紧跟'ing'后面的字符串前加上'AAA'
print re.sub(r'(?<=ing)(\w+)\b',r'AAA\1',s)
# 输出: goingAAAxxx

随机推荐

  1. Building Maintainable Software-java篇之Separate Concerns in Modules

    Building Maintainable Software-java篇之Separate Concerns in Modules   In a system that is both complex ...

  2. Spring MVC文件上传教程

    1- 介绍 这篇教程文章是基于 Spring MVC来实现文件的上传功能,这里主要是实现两个功能:1.上传单个文件并将其移动到对应的上传目录:2.一次上传多个文件并将它们存储在指定文件夹下,接下来我们 ...

  3. Eclipse 浏览(Navigate)菜单

    浏览 Eclipse 工作空间 浏览(Navigate)菜单提供了多个菜单可以让你快速定位到指定资源. 上图中 Open Type, Open Type in Hierarchy 和 Open Res ...

  4. python 案例:使用BeautifuSoup4的爬虫

    我们以腾讯社招页面来做演示:http://hr.tencent.com/position.php?&start=10#a 使用BeautifuSoup4解析器,将招聘网页上的职位名称.职位类别 ...

  5. Vmware私有云虚拟机(CentOS 6.5 OS)之根分区扩容

    注:适用于未使用lvm管理的分区,目前仅在CentOS 6.5 上操作,其他系统尚未测试,请谨慎操作 一.查看当前分区状况 [root@disk-test ~]# df -h Filesystem   ...

  6. 求伪逆矩阵c++代码(Eigen库)

    非方阵的矩阵的逆矩阵  pseudoInverse 伪逆矩阵是逆矩阵的广义形式,广义逆矩阵 matlab中是pinv(A)-->inv(A). #include "stdafx.h&q ...

  7. NDK工具制作

    NDK工具制作 - Generate a stand-alone toolchain of the NDK, example: `./android-ndk-r10/build/tools/make- ...

  8. IO-Polling的代码分析

    在前一篇文章<IO-Polling实现分析与性能评測>中提到了IO-Polling与中断的原理差别,并通过两种模式下NVMe SSD的性能測试对两者进行了对照. 这篇文章将深入到IO-Po ...

  9. Andriod - 创建自定义控件

    控件和布局的继承结构: 可以看到,我们所用的所有控件都是直接或间接继承自 View的,所用的所有布局都是直接或间接继承自 ViewGroup 的.View 是 Android 中一种最基本的 UI 组 ...

  10. ie10 css hack 条件注释等兼容方式整理

    点评:ie10已经上线一段时间了,相信已经有一部分前端潮人体验过了,截至到现在,在ie6到ie9的浏览器各种各样的古怪行为,开发人员不得不使用条件注释,有条件的类,和其他特定于IE的css hack来 ...