$python正则表达式系列(5)——零宽断言
本文主要总结了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
随机推荐
- Android Studio 使用笔记:[转] Mac下修改Android Studio 所用的JDK版本
原文链接:http://www.jianshu.com/p/d8d1d72d0248# 最近项目从Eclipse+Ant构建模式转移到了Android Studio+Gradle构建模式,自然的JDK ...
- MFC使用自带的MSXML6.dll解析xml(开发环境vc2010)
程序是win32控制台程序 // msxml.cpp : 定义控制台应用程序的入口点. #include "stdafx.h" #include <iostream> ...
- scala 系列文章汇总
本文作为scala系列文章索引 本博客目录: case class 背后的秘密 以spark源码为参照分析模式匹配及种类 另外,本文还收录了几个作者认为比较好的博文或网站: scala 相关网址汇总 ...
- To Be a Dog Man
作为一个多年的篮球爱好者,多年保持者对于足球的偏见. 也许不了解也是偏见開始的地方. 直到我最终看到了那个男人---梅西 梅西:一个犬人,一个病人 视频作者是阿根廷专栏作家Hernam Casciar ...
- 使用.NET Reflector 查看Unity引擎里面的DLL文件
转载标注来源哦! 原地址在这里!在这里. 当你查看unity里面API的时候.是不是有时候追踪了一两步就碰到DLL文件走不下去了呢?非常是不爽吧. 这样的问题我也是常常碰到.这是人家商业引擎不想让你看 ...
- asp.net 页面延时五秒,跳转到另外的页面
asp.net 页面延时五秒,跳转到另外的页面的实现代码. --前台 <%@ Page Language="C#" AutoEventWireup="true&qu ...
- poj 1548(最小路径覆盖)
题目链接:http://poj.org/problem?id=1548 思路:最小路径覆盖是很容易想到的(本题就是求最小的路径条数覆盖所有的点),关键是如何建图,其实也不难想到,对于当前点,如果后面的 ...
- 看懂Gradle脚本(4)- Groovy语法之运算符重载
继续讨论Task定义 回想一下前一篇文章的样例: task myTask { doLast { println 'hello world!' } } 这段脚本定义了一个名为myTask的任务.而且通过 ...
- 谈抽象1——无脑copy等于自杀
近期被外派帮助国内某公司做政府某部门OA系统.听说他们那有个成熟的java框架,使用了非常长时间,抱着学习的态度,我进入这个公司.当我熟悉了一周后,留下了非常多疑问,而这些疑问,也诱发了这次关于&qu ...
- c++ 继承,组合
.什么是继承 A继承B,说明A是B的一种,并且B的所有行为对A都有意义 eg:A=WOMAN B=HUMAN A=鸵鸟 B=鸟 (不行),因为鸟会飞,但是鸵鸟不会. .什么是组合 若在逻辑上A是B的“ ...