Python正则表达式中的零宽断言
概念
先了解下断言是什么?
断言在编程和逻辑中是一种条件检查,用来验证某个表达式是否为真。
如果条件为真,程序继续执行。
如果条件为假,则通常会抛出一个错误或异常。
Python中的断言关键字有assert
,用来检查一个表达式是否为真。
可以理解为简单的if语句或者三元运算符a =True if x<y else False
。
例如:
assert 2>1 # 条件为 true 正常执行
assert 1>2 # 条件为 false 触发异常
"""
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AssertionError
"""
而正则的零宽断言就是一种类似的条件检查,不过是对字符串位置条件的检查。
应用场景
之前示例不够具体,2024年6月26日进行一定修订
先说个简单的例子
如果我要提取下面示例中的 ;
后面的 456-789
,该怎么做?
首先尝试使用\d{3}-\d{3}
,可以提取但不能一步到位
import re
s = "456-123,123;456-789,123"
m = re.findall(r"\d{3}-\d{3}", s)
print(m) # ['456-123', '456-789']
尝试包含;
,也可以,但还是要进一步操作,使用replace
替换掉;
import re
s = "456-123,123;456-789,123"
m = re.findall(r";\d{3}-\d{3}", s)
print(m) # [';456-789']
print(m[0].replace(";", "")) # 456-789
有没有一种一步到位的操作?即在表达式中包含需要匹配的项,但是匹配结果又不出现匹配项
例如正则表达式有;
,但是匹配结果没有;
这就是今天要讲的正则中的零宽断言
上面的问题,可以使用后发断言 (?<=)
进行提取
import re
s = "000,123;456-789"
m = re.search(r"(?<=;)\d+", s)
print(m.group()) # 456
断言在正则表达式中非常有用,我们希望匹配表达式,但又不希望表达式包含在匹配结果中就可以用到。
类型
python中正则的零宽断言有4种
(?=)
:先行断言(?!)
:否定先行(?<=)
:后发断言(?<!)
:否定后发断言
名称的问题
在不同的开发语言中称呼不同
例如,先行断言有的叫正向零宽先行断言,有的叫零宽正先行断言
但其实本质相同,例如土豆与马铃薯是一种东西,我这里使用来简称,大致知道这么回事即可
先行断言
先行断言(?=)
用于检测一个表达式后面是否符合条件
例如这里我希望提取,
前面的2000,那么写2000(?=,)
,从而提取2000
这里表示确保”2000”后面跟着一个逗号,但不会将逗号包括在匹配结果中。
```python
import re
text = "2000,3000,4000,5000,2000x"
pattern = r"2000(?=,)"
match = re.search(pattern, text)
if match:
print(match.group()) # 2000
```
否定先行断言
否定先行断言(?!)
,用于检测一个表达式后面是否不符合条件
其实就是先行断言取反的检测
例如这里有2个2000,我希望提取,
前面的2000的字符串,那么写\d+2000(?!;)
,从而提取12000
这里表示确保包含”2000”结尾的数字字符串后面不能跟着;
,但不会将;
包括在匹配结果中。
import re
text = "12000,3000,22000;5000,2000x"
pattern = r"\d+2000(?!;)"
match = re.search(pattern, text)
if match:
print(match.group()) # 12000
后发断言
后发断言(?<=)
,用于检测一个表达式前面是否符合条件
例如,我们要找abc
的字符串,但前面的字符串必须是some
,那么写(?<=some)\w+
,从而提取abc456
这里表示确保abc的前面是some
,并且some
不包含在匹配结果中
import re
text = "defabc123 someabc456 otherdefabc789"
matches = re.search(r"(?<=some)abc\d+", text)
if matches:
print(matches.group()) # abc456
否定后发断言
否后发断言(?<!)
,用于检测一个表达式前面是否不符合条件
例如,我们要找abc
的字符串,但前面的字符串不能是def
,那么写(?<=def)\w+
,从而提取abc456
这里表示确保abc的前面不是def
,并且def
不包含在匹配结果中
import re
text = "defabc123 someabc456 otherdefabc789"
matches = re.search(r"(?<!def)abc\d+", text)
if matches:
print(matches.group()) # abc456
理解与运用
有些人可能会觉得有点绕,太麻烦了吧,什么先行,什么后发。
要去记这个东西,个人一开始也是这么想的,尤其当时学 js 提到这个。(名称更加绕)
- 正向零宽后发断言
- 负向零宽后发断言
- 正向零宽先行断言
- 负向零宽先行断言
要运用函数,肯定要先记住函数名。
而要去记这个东西,头大!所以上面 Python都是写了简称
其实可以这么理解,都是字面意思取反
先行的就是检测后面,后发就是检测前面,加了否定就是条件取反
也算是一点记忆小技巧