Python正则表达式中的零宽断言

概念

先了解下断言是什么?

断言在编程和逻辑中是一种条件检查,用来验证某个表达式是否为真。
如果条件为真,程序继续执行。
如果条件为假,则通常会抛出一个错误或异常。

Python中的断言关键字有assert,用来检查一个表达式是否为真。
可以理解为简单的if语句或者三元运算符a =True if x<y else False

例如:

1
2
3
4
5
6
7
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},可以提取但不能一步到位

1
2
3
4
5
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替换掉;

1
2
3
4
5
6
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

有没有一种一步到位的操作?即在表达式中包含需要匹配的项,但是匹配结果又不出现匹配项
例如正则表达式有;,但是匹配结果没有;
这就是今天要讲的正则中的零宽断言

上面的问题,可以使用后发断言 (?<=) 进行提取

1
2
3
4
import re
s = "000,123;456-789"
m = re.search(r"(?<=;)\d+", s)
print(m.group()) # 456

断言在正则表达式中非常有用,我们希望匹配表达式,但又不希望表达式包含在匹配结果中就可以用到。

类型

python中正则的零宽断言有4种

  • (?=):先行断言
  • (?!):否定先行
  • (?<=):后发断言
  • (?<!):否定后发断言

名称的问题

在不同的开发语言中称呼不同

例如,先行断言有的叫正向零宽先行断言,有的叫零宽正先行断言
但其实本质相同,例如土豆与马铃薯是一种东西,我这里使用来简称,大致知道这么回事即可

先行断言

先行断言(?=)用于检测一个表达式后面是否符合条件
例如这里我希望提取,前面的2000,那么写2000(?=,),从而提取2000
这里表示确保”2000”后面跟着一个逗号,但不会将逗号包括在匹配结果中。

1
2
3
4
5
6
7
8
9
```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”结尾的数字字符串后面不能跟着;,但不会将;包括在匹配结果中。

1
2
3
4
5
6
7
8
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不包含在匹配结果中

1
2
3
4
5
6
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不包含在匹配结果中

1
2
3
4
5
6
import re

text = "defabc123 someabc456 otherdefabc789"
matches = re.search(r"(?<!def)abc\d+", text)
if matches:
print(matches.group()) # abc456

理解与运用

有些人可能会觉得有点绕,太麻烦了吧,什么先行,什么后发。
要去记这个东西,个人一开始也是这么想的,尤其当时学 js 提到这个。(名称更加绕)

  • 正向零宽后发断言
  • 负向零宽后发断言
  • 正向零宽先行断言
  • 负向零宽先行断言

要运用函数,肯定要先记住函数名。
而要去记这个东西,头大!所以上面 Python都是写了简称

其实可以这么理解,都是字面意思取反
先行的就是检测后面,后发就是检测前面,加了否定就是条件取反
也算是一点记忆小技巧


Python正则表达式中的零宽断言
https://linguoguang.com/2024/06/13/Python正则表达式中的零宽断言/
作者
linguoguang
发布于
2024年6月13日
许可协议