非捕获组命名组

精心设计的正则表达式可能会划分很多组,这些组不仅可以匹配相关的子串,还能够对正则表达式本身进行分组和结构化。在复杂的正则表达式中,由于有太多的组,因此通过组的序号来跟踪和使用会变得困难。有两个新的功能可以帮你解决这个问题——非捕获组和命名组——它们都使用了一个公共的正则表达式扩展语法。我们先来看看这个表达式扩展语法是什么。

正则表达式的扩展语法

众所周知,Perl 5 为标准的正则表达式增加了许多强大的功能。Perl 的开发者们并不能选择一个新的元字符或者通过反斜杠构造一个新的特殊序列来实现扩展的功能。因为这样会和标准的正则表达式发生冲突。比如你想选择 & 作为扩展功能的元字符(在标准正则表达式中,& 没有特殊意义),但这样的话,已经按照标准语法写出来的正则表达式就不得不修改,因为它们中包含的 '&' 意愿上只是把它当做普通字符来匹配而已。

小甲鱼解释:看起来很是头疼的兼容性问题,Perl 的开发者们是如何解决的呢?请接着看......

最终,Perl 的开发者们决定使用 (?...) 作为扩展语法。问号 ? 紧跟在左小括号 ( 后边,本身是一个语法错误的写法,因为 ?前边没有东西可以重复,所以这样就解决了兼容性的问题(理由是语法正确的正则表达式肯定不会这么写嘛~)。然后,紧跟在 ? 后边的字符则表示哪些扩展语法会被使用。例如 (?=foo) 表示一种新的扩展功能(前向断言),(?:foo) 则表示另一种扩展功能(一个包含子串 foo 的非捕获组)。

Python 支持 Perl 的一些扩展语法,并且在此基础上还增加了一个扩展语法。如果紧跟在问号 ? 后边的是 P,那么可以肯定这是一个 Python 的扩展语法。

好,既然我们已经知道了如何对正则表达式的标准语法进行扩展,那我们回来看看这些扩展语法在复杂的正则表达式中是如何应用的。

非捕获组

第一个我们要讲的是非捕获组。有时候你知识需要用一个组来表示部分正则表达式,你并不需要这个组去匹配任何东西,这时你可以通过非捕获组来明确表示你的意图。非捕获组的语法是 (?:...),这个 ... 你可以替换为任何正则表达式。

  1. >>> m = re.match("([abc])+", "abc")
  2. >>> m.groups()
  3. ('c',)
  4. >>> m = re.match("(?:[abc])+", "abc")
  5. >>> m.groups()
  6. ()

复制代码

小甲鱼解释:“捕获”就是匹配的意思啦,普通的子组都是捕获组,因为它们能从字符串中匹配到数据。

除了你不能从非捕获组获得匹配的内容之外,其他的非捕获组跟普通子组没有什么区别了。你可以在里边放任何东西,使用重复功能的元字符,或者跟其他子组进行嵌套(捕获的或者非捕获的子组都可以)。

当你需要修改一个现有的模式的时候,(?:...) 是非常有用的。原始是添加一个非捕获组并不会影响到其他(捕获)组的序号。值得一提的是,在搜索的速度上,捕获组和非捕获组的速度是没有任何区别的。

命名组

我们再来看另外一个重要功能:命名组。普通子组我们使用序列来访问它们,命名组则可以使用一个有意义的名字来进行访问。

命名组的语法是 Python 特有的扩展语法:(?P<name>)。很明显,< > 里边的 name 就是命名组的名字啦。命名组除了有一个名字标识之外,跟其他捕获组是一样的。

匹配对象的所有方法不仅可以处理那些由数字引用的捕获组,还可以处理通过字符串引用的命名组。除了使用名字访问,命名组仍然可以使用数字序号进行访问:

  1. >>> p = re.compile(r'(?P<word>\b\w+\b)')
  2. >>> m = p.search( '(((( Lots of punctuation )))' )
  3. >>> m.group('word')
  4. 'Lots'
  5. >>> m.group(1)
  6. 'Lots'

复制代码

命名组非常好用,因为它让你可以使用一个好记的名字代替一些毫无意义的数字。下边是来自 imaplib 模块的例子:

  1. InternalDate = re.compile(r'INTERNALDATE "'
  2. r'(?P<day>[ 123][0-9])-(?P<mon>[A-Z][a-z][a-z])-'
  3. r'(?P<year>[0-9][0-9][0-9][0-9])'
  4. r' (?P<hour>[0-9][0-9]):(?P<min>[0-9][0-9]):(?P<sec>[0-9][0-9])'
  5. r' (?P<zonen>[-+])(?P<zoneh>[0-9][0-9])(?P<zonem>[0-9][0-9])'
  6. r'"')

复制代码

很明显,使用 m.group('zonem') 访问匹配内容要比使用数字 9 更简单明了。

正则表达式中,反向引用的语法像 (...)\1 是使用序号的方式来访问子组;在命名组里,显然也是有对应的变体:使用名字来代替序号。其扩展语法是 (?P=name),含义是该 name 指向的组需要在当前位置再次引用。那么搜索两个单词的正则表达式可以写成 (\b\w+)\s+\1,也可以写成 (?P<word>\b\w+)\s+(?P=word):

  1. >>> p = re.compile(r'(?P<word>\b\w+)\s+(?P=word)')
  2. >>> p.search('Paris in the the spring').group()
  3. 'the the'

复制代码

前向断言

我们要讲解的另一个零宽断言是前向断言,前向断言可以分为前向肯定断言和前向否定断言两种形式。

(?=...)

前向肯定断言。如果当前包含的正则表达式(这里以 ... 表示)在当前位置成功匹配,则代表成功,否则失败。一旦该部分正则表达式被匹配引擎尝试过,就不会继续进行匹配了;剩下的模式在此断言开始的地方继续尝试。

(?!...)

前向否定断言。这跟前向肯定断言相反(不匹配则表示成功,匹配表示失败)。

为了使大家更易懂,我们举个例子来证明这玩意是真的很有用。大家考虑一个简单的正则表达式模式,这个模式的作用是匹配一个文件名。我们都知道,文件名是用 . 将名字和扩展名分隔开的。例如在 fishc.txt 中,fishc 是文件的名字,.txt 是扩展名。

这个正则表达式其实挺简单的:

.*[.].*$

注意,这里用于分隔的 . 是一个元字符,所以我们使用 [.] 剥夺了它的特殊功能。还有 $,我们使用 $ 确保字符串剩余的部分都包含在扩展名中。所以这个正则表达式可以匹配 fishc.txt,foo.bar,autoexec.bat,sendmail.cf,printers.conf 等。

现在我们来考虑一种复杂一点的情况,如果你想匹配扩展名不是 bat 的文件,你的正则表达式应该怎么写呢?
我们先来看下你有可能写错的尝试:

.*[.][^b].*$

这里为了排除 bat,我们先尝试排除扩展名的第一个字符为非 b。但这是错误的开始,因为 foo.bar 后缀名的第一个字符也是 b。

为了弥补刚刚的错误,我们试了这一招:

.*[.]([^b]..|.[^a].|..[^t])$

我们不得不承认,这个正则表达式变得很难看......但这样第一个字符不是 b,第二个字符不是 a,第三个字符不是 t......这样正好可以接受 foo.bar,排除 autoexec.bat。但问题又来了,这样的正则表达式要求扩展名必须是三个字符,比如sendmail.cf 就会被排除掉。

好吧,我们接着修复问题:

.*[.]([^b].?.?|.[^a]?.?|..?[^t]?)$

在第三次尝试中,我们让第二个和第三个字符变成可选的。这样就可以匹配稍短的扩展名,比如 sendmail.cf。

不得不承认,我们把事情搞砸了,现在的正则表达式变得艰涩难懂外加奇丑无比!!

更惨的是如果需求改变了,例如你想同时排除 bat 和 exe 扩展名,这个正则表达式模式就变得更加复杂了......

当当当当!主角登场,其实,一个前向否定断言就可以解决你的难题:

.*[.](?!bat$).*$

我们来解释一下这个前向否定断言的含义:如果正则表达式 bat 在当前位置不匹配,尝试剩下的部分正则表达式;如果 bat匹配成功,整个正则表达式将会失败(因为是前向否定断言嘛^_^)。(?!bat$) 末尾的 $ 是为了确保可以正常匹配像sample.batch 这种以 bat 开始的扩展名。

同样,有了前向否定断言,要同时排除 bat 和 exe 扩展名,也变得相当容易:

.*[.](?!bat$|exe$).*$

Python3 如何优雅地使用正则表达式(详解五)的更多相关文章

  1. JavaScript正则表达式详解(一)正则表达式入门

    JavaScript正则表达式是很多JavaScript开发人员比较头疼的事情,也很多人不愿意学习,只是必要的时候上网查一下就可以啦~本文中详细的把JavaScript正则表达式的用法进行了列表,希望 ...

  2. JavaScript正则表达式详解(二)JavaScript中正则表达式函数详解

    二.JavaScript中正则表达式函数详解(exec, test, match, replace, search, split) 1.使用正则表达式的方法去匹配查找字符串 1.1. exec方法详解 ...

  3. Java 正则表达式详解_正则表达式

    body{ font-family: "Microsoft YaHei UI","Microsoft YaHei",SimSun,"Segoe UI& ...

  4. Django url配置 正则表达式详解 分组命名匹配 命名URL 别名 和URL反向解析 命名空间模式

    Django基础二之URL路由系统 本节目录 一 URL配置 二 正则表达式详解 三 分组命名匹配 四 命名URL(别名)和URL反向解析 五 命名空间模式 一 URL配置 Django 1.11版本 ...

  5. 【python3+request】python3+requests接口自动化测试框架实例详解教程

    转自:https://my.oschina.net/u/3041656/blog/820023 [python3+request]python3+requests接口自动化测试框架实例详解教程 前段时 ...

  6. (转)Python3.5——装饰器及应用详解

    原文:https://blog.csdn.net/loveliuzz/article/details/77853346 Python3.5——装饰器及应用详解(下)----https://blog.c ...

  7. (转)linux正则表达式详解

    linux正则表达式详解 http://blog.csdn.net/wuliowen/article/details/64131815 1:什么是正则表达式: 简单的说,正则表达式就是处理字符串的方法 ...

  8. Python3、setuptools、Pip3安装详解

    Python3.setuptools.Pip3安装详解 2017年08月19日 18:58:47 安静的技术控 阅读数:26002    版权声明:本文为博主原创文章,未经博主允许不得转载. http ...

  9. Linux文本处理三剑客之grep及正则表达式详解

    Linux文本处理三剑客之grep及正则表达式详解 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.Linux文本处理三剑客概述 grep: 全称:"Global se ...

  10. Django路由配置之正则表达式详解

    正则表达式详解 urls.py from django.conf.urls import url from . import views urlpatterns = [ url(r'^articles ...

随机推荐

  1. 【转】android去掉EditView的默认焦点问题

    原文网址:http://www.111cn.net/sj/android/54680.htm 做一个输入框时发现android中EditView的默认焦点了,这种问题如果是在输入框还好,但在搜索页面或 ...

  2. ubuntu14.04 为Firefox安装flash插件

    Ubuntu系统装好后,发现火狐浏览器播放不了视频,一直提示安装flash,但按照火狐浏览器上的提示Flash插件安装总是失败,那就只能手动安装了. (1) 去flash官网:http://get.a ...

  3. linux下svn修改用户名和密码

    1.临时更换,在命令下强制加上 --username 和--password选项, 例如:svn up --username zhangsan --password 123456 svn co URL ...

  4. uglifyjs压缩js文件(指令压缩/ 批量压缩/ 编程方式压缩)

    一.指令压缩 1.安装node,npm——详细见nodejs安装与使用入门 2.安装 uglifyjs——npm install -g uglify-js 3.压缩例子:1)uglifyjs  mai ...

  5. Matlab:回归分析(2)

    方法一:直接用多元二项式回归 %数据输入 x1 = [ ]; x2 = [ ]; %多元二项式回归函数 y = [ ]; X = [x1' x2']; Y = y'; rstool(X, Y, 'pu ...

  6. 封装实现UIButton左文字右图片

    #import "TitleButton.h" @implementation TitleButton - (instancetype)initWithFrame:(CGRect) ...

  7. 夏普比率(Sharpe Ratio)

    投资中有一个常规的特点,即投资标的的预期报酬越高,投资人所能忍受的波动风险越高:反之,预期报酬越低,波动风险也越低.所以理性的投资人选择投资标的与投资组合的主要目的为:在固定所能承受的风险下,追求最大 ...

  8. motan源码分析二:使用spi机制进行类加载

    在motan的源码中使用了很多的spi机制进行对象的创建,下面我们来具体分析一下它的实现方法. 1.在实际的jar包的\META-INF\services目录中引入相关的文件,例如下图中,我解压了co ...

  9. Node.js初体验

    1.Node.js是什么 [1]Node是一个server端 JavaScript 解释器,但是真的以为JavaScript不错的同学学习Node就能轻松拿下,那么你就错了.总结:水深不深我还不知道, ...

  10. MySQL高可用基础之keepalived+双主复制【转】

    环境:MySQL-VIP:192.168.1.3MySQL-master1:192.168.1.1MySQL-master2:192.168.1.2 OS版本:CentOS release 6.4 ( ...