最新消息:20210816 当前crifan.com域名已被污染,为防止失联,请关注(页面右下角的)公众号

【教程】详解Python正则表达式之: (?(id/name)yes-pattern|no-pattern) 条件性匹配

Python re crifan 5793浏览 0评论

Python 2.7的手册中,官方的解释为:

(?(id/name)yes-pattern|no-pattern)

Will try to match with yes-pattern if the group with given id or name exists, and with no-pattern if it doesn’t. no-pattern is optional and can be omitted. For example, (<)?(\w+@\w+(?:\.\w+)+)(?(1)>) is a poor email matching pattern, which will match with '<[email protected]>' as well as '[email protected]', but not with '<[email protected]'.

New in version 2.4.

下面先来详细解释一下这段话的含义:

1.(?(id/name)yes-pattern|no-pattern) 的作用是:

  • 对于给出的id或者name,先尝试去匹配 yes-pattern部分的内容;
  • 如果id或name条件不满足,则去匹配no-pattern部分的内容;

这句话听着还是很拗口的,或者说一下子还是很难懂的。至少我之前第一次,以及后来的很多次,看了之后,也一样没有完全搞懂。

后来经过一些实践,算是终于有所了解其中的含义了。

所以此处有必要,再详细解释一下:

此处的name或id,是针对(当前位置的)条件性匹配之前的,某个已经通过group去分组的内容

其中:

如果是有命名的分组,即named group,则对应的该分组就有对应的name,即此处所指的就是对应的name;

如果是无命名的分组,即unnamed group,则对应的该分组也有对应的分组的编号,称为group的number,也叫做id,对应的就是这里的id;

 

注:

相关内容,如果不了解,请先去看:

【教程】详解Python正则表达式之: (?P<name>…) named group 带命名的组

【教程】详解Python正则表达式之: (…) group 分组

 

所以,搞清楚了这里的id或name,才好理解这段话。

接着解释就是,前面已经通过对应的group去匹配对应的内容了,对应的有group的id或name,

如果前面的group匹配成功,则此处,就执行yes-pattern的匹配;

如果前面group匹配不成功,即没有找到符合该的group内容,则就匹配no-pattern;

 

其中,yes-pattern和no-pattern,都是对应的,普通的正则表达式,用来匹配所需的内容。

 

此时,才算基本明白其所要说的含义了。

 

2.根据语法,很明显,如果存在no-pattern,则前面要有个竖杠’|’,用来分隔yes-pattern和no-pattern

如果此处,不想写,不需要写no-pattern,则也是可以的,也是可以省略掉的,对应的,也就不需要再写那个竖杠’|’了。

 

3.上面给出了一个例子:

(<)?(\w+@\w+(?:\.\w+)+)(?(1)>)

此处,还是先来解释解释吧。

其可以分解为:

  • (<)?:就是匹配小于号这个符号,后面的?表示可以有,可以没有;
  • (\w+@\w+(?:\.\w+)+):最外层的括号内是\w+@\w+(?:\.\w+)+,其又可以分为:
    • \w+@\w+:\w+表示尽可能多的匹配字母数字下划线,中间是匹配@字符本身,所以就是希望匹配到xxx@xxx
    • (?:\.\w+)+:前面是括号括起来的一组,加号+表示尽可能多的匹配括号内的。看完下面解释就明白了,此处就是匹配尽可能多的.xxx的内容
  • (?(1)>) :此处,就是真正我们此处最关系的,选择性匹配了。参考本身的语法:(?(id/name)yes-pattern|no-pattern) ,此处中的1,就是对应id,即之前的第一个组,即(<);而(1)后面的>,就是此处的yes-pattern,而此处很明显,no-pattern是被省略了。所以没有’|’。所以,此处的含义就很清楚了,就是去匹配,如果最开始的编号为1的那个组,即(<)存在的话,即前面能找到<的话,此处后面也就去匹配有没有>,如果前面没有<,则后面就什么也不匹配。正真是符号我们所期望的,要么是[email protected],要么是[email protected]

很显然,此处这种匹配规则,是很不严谨的,对于很多复杂的邮箱地址,没法匹配到。所以,此处才说是“poor email matching pattern”,即相对是一个不是那么好的email的匹配规则。

至此,终于把此处的内容解释完毕了。

但是,对于选择性匹配,还是没有很深刻的理解。

下面,就通过详细的代码,来更加深入的解释,选择性匹配,有什么用,以及如何用。


下面,就以之前给某人写的一个正则表达式为例,来说明,什么是条件性匹配。

先说,关于有些人不清楚,觉得选择性匹配,有何用。那么此处,就来举个例子,相对比较实用的例子:

希望去写一个匹配规则,匹配用于输入的数字,其中,输入的内容只能是数字,也可能有小数点,如果有小数点,则小数点后面,此处特意限制为最多2位。

针对如上的需求,就可以用到选择性匹配去实现了:

如下是详细的代码演示:

#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
【教程】详解Python正则表达式之: (?(id/name)yes-pattern|no-pattern) 条件性匹配
https://www.crifan.com/detailed_explanation_about_python_regular_express_yes_or_no_conditional_match

Version:    2012-11-17
Author:     Crifan
"""

import re;

#需求:
#类似于检测(最多两位小数的)数字的合法性:
#所有的字符都是数字
#如果有小数点,那么小数点后面最多2位数字
testNumStrList = {
    #合法的数字
    '12.34',
    '123.4',
    '1234',
    
    #非法的数字
    '1.234',
    '12.',
    '12.ab',
    '12.3a',
    '123abc',
    '123abc456',
}
for eachNumStr in testNumStrList:

    #下面这个是不严谨的,会导致:
    #1.234 -> 只会去判断234,所以检测出整数部分是234,无小数
    #123.4 -> 只会去判断4,所以检测出整数部分是4,无小数
    #123abc456 -> 只会去判断456,所以检测出整数部分是456,无小数
    #foundValidNumStr = re.search("(?P<integerPart>\d+)(?P<foundPoint>\.)?(?P<decimalPart>(?(foundPoint)\d{1,2}))$", eachNumStr);
    
    #下面这个也是不严谨的,会导致:
    #1.234 -> 只去判断1.23,所以检测出整数是1,小数是23
    #12. -> 只会去判断12,所以检测出整数是12,无小数
    #123abc456 -> 只会去判断123,所以检测出整数是123,无小数
    #12.ab -> 只会去判断12,所以检测出整数是12,无小数
    #123abc -> 只会去判断123,所以检测出整数是123,无小数
    #12.3a -> 只会去判断12.3,所以检测出整数是12,小数是3
    #foundValidNumStr = re.search("^(?P<integerPart>\d+)(?P<foundPoint>\.)?(?P<decimalPart>(?(foundPoint)\d{1,2}))", eachNumStr);

    #下面这个,更不严谨,会导致中间只要有数字,那么基本上都会去匹配到,和实际的期望,差距最大
    #foundValidNumStr = re.search("(?P<integerPart>\d+)(?P<foundPoint>\.)?(?P<decimalPart>(?(foundPoint)\d{1,2}))", eachNumStr);

    #下面这个才是正确的
    foundValidNumStr = re.search("^(?P<integerPart>\d+)(?P<foundPoint>\.)?(?P<decimalPart>(?(foundPoint)\d{1,2}))$", eachNumStr);
    #也可以写成下面这样:
    #foundValidNumStr = re.search("^(?P<integerPart>\d+)(\.)?(?P<decimalPart>(?(2)\d{1,2}))$", eachNumStr); #这个也是同样的效果
    
    #print "foundValidNumStr=",foundValidNumStr;
    if(foundValidNumStr):
        integerPart = foundValidNumStr.group("integerPart");
        decimalPart = foundValidNumStr.group("decimalPart");
        print "eachNumStr=%s\tis valid numebr ^_^, integerPart=%s, decimalPart=%s"%(eachNumStr, integerPart, decimalPart);
    else:
        print "eachNumStr=%s\tis invalid number !!!"%(eachNumStr);

 

当然,对于上述的正则表达式,即使理解了,但是可能很多人还是对于,如何从无到有的去写出来,觉得无从下手,所以,可以再去参考这个帖子:

【教程】以Python中的re模块为例,手把手教你,如何从无到有,写出相对复杂的正则表达式

 

【总结】

通过上面的代码,我们就更加容易理解了,对应的,选择性匹配组,是如何实现的。

实际中,当有类似的情况时,我们就可以充分利用这个高级搜索规则,去轻松的匹配,获得我们所需的内容了。

转载请注明:在路上 » 【教程】详解Python正则表达式之: (?(id/name)yes-pattern|no-pattern) 条件性匹配

发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
90 queries in 0.223 seconds, using 22.13MB memory