正则表达式中的零宽断言(Zero-Length Assertions)
前言
今天碰到一个问题,需要将 camel case 的字符串转成 snake case,因为功能比较简单就懒得用一个三方库了。
写的过程中发现实现这种功能真的有很多方法,其中使用零宽断言是最优雅的方式。
def snake_case(s):
return re.sub(r'(?<!^)(?=[A-Z])', '_', s).lower()
零宽断言(Zero-Length Assertions)
上面的代码,通过一个 re.sub 函数就可以实现 snake_case 真的非常优雅,现在我们来看看零宽断言到底是什么。
正则表达式可以支持向前查找(Lookahead)和向后查找(Lookbehind),向前查找和向后查找是不消耗字符的,也就是说零宽断言只是匹配一个”位置”。
这也是为什么称为零宽。
上面的 snake_case 函数里,re.sub 是将匹配到的字符串替换成指定的字符串,但是你会发现这里写的 ’_’ 并没有替换任何字符串,因为它匹配的是一个位置。
不同的查找方式
(?=):正向(向后)查找指定字符串的位置
(?!):正向(向后)查找非指定字符串的位置
# 查找大写字母的位置,并且再该位置塞入一个下划线
print(re.sub(r'(?=[A-Z])', '_', 'abCdeC'))
# 返回:ab_Cde_C
# 查找字符串 b,只查 后面跟着一个大写字母的 b,并将 b 替换成下划线
print(re.sub(r'b(?=[A-Z])', '_', 'abCdbeC'))
# 返回:a_CdbeC
# 查找所有不是大写字母的位置
print(re.sub(r'(?![A-Z])', '_', 'abCdeC'))
# 返回:_a_bC_d_eC_
# 查找字符串 b,只查后面不跟着大写字母的 b,并将 查到的 b 替换成下划线
print(re.sub(r'b(?=[A-Z])', '_', 'abCdbeC'))
# 返回:abCd_eC
(?≤):反向(向前)查找指定字符串的位置
(?!≤):反向(向前)查找非指定字符串的位置
# 查找以大写字母开头的 d,并将 d 替换成下划线
print(re.sub(r'(?<=[A-Z])d', '_', 'abCdbeC'))
# 返回:abC_beC
# 查找不以大写字母开头的 d,并将 d 替换成下划线
print(re.sub(r'(?<![A-Z])d', '_', 'abCdbedC'))
# 返回:abCdbe_C
这里要注意的是,向前和向后不是以你的代码位置为准,例如(?=[A-Z])b 你可能会觉得这里是匹配大写字母开头的 b,实际上这一行匹配不到任何内容,因为?= 只能向后匹配