热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Python3如何优雅地使用正则表达式(详解五)

非捕获组命名组精心设计的正则表达式可能会划分很多组,这些组不仅可以匹配相关的子串,还能够对正则表达式本身进行分组和结构化。在复杂的正则表达式中,由于有太多的组,因此通过组的序号来跟踪
非捕获组命名组

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


正则表达式的扩展语法

众所周知,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  就是命名组的名字啦。命名组除了有一个名字标识之外,跟其他捕获组是一样的。

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

  1. >>> p = re.compile(r'(?P\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[ 123][0-9])-(?P[A-Z][a-z][a-z])-'
  3.         r'(?P[0-9][0-9][0-9][0-9])'
  4.         r' (?P[0-9][0-9]):(?P[0-9][0-9]):(?P[0-9][0-9])'
  5.         r' (?P[-+])(?P[0-9][0-9])(?P[0-9][0-9])'
  6.         r'"')

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

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

  1. >>> p = re.compile(r'(?P\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$).*$



推荐阅读
  • 本文介绍了在Linux下安装Perl的步骤,并提供了一个简单的Perl程序示例。同时,还展示了运行该程序的结果。 ... [详细]
  • 本文介绍了RPC框架Thrift的安装环境变量配置与第一个实例,讲解了RPC的概念以及如何解决跨语言、c++客户端、web服务端、远程调用等需求。Thrift开发方便上手快,性能和稳定性也不错,适合初学者学习和使用。 ... [详细]
  • 本文介绍了如何使用python从列表中删除所有的零,并将结果以列表形式输出,同时提供了示例格式。 ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • 本文介绍了在CentOS上安装Python2.7.2的详细步骤,包括下载、解压、编译和安装等操作。同时提供了一些注意事项,以及测试安装是否成功的方法。 ... [详细]
  • Java和JavaScript是什么关系?java跟javaScript都是编程语言,只是java跟javaScript没有什么太大关系,一个是脚本语言(前端语言),一个是面向对象 ... [详细]
  • 恶意软件分析的最佳编程语言及其应用
    本文介绍了学习恶意软件分析和逆向工程领域时最适合的编程语言,并重点讨论了Python的优点。Python是一种解释型、多用途的语言,具有可读性高、可快速开发、易于学习的特点。作者分享了在本地恶意软件分析中使用Python的经验,包括快速复制恶意软件组件以更好地理解其工作。此外,作者还提到了Python的跨平台优势,使得在不同操作系统上运行代码变得更加方便。 ... [详细]
  • 本文整理了315道Python基础题目及答案,帮助读者检验学习成果。文章介绍了学习Python的途径、Python与其他编程语言的对比、解释型和编译型编程语言的简述、Python解释器的种类和特点、位和字节的关系、以及至少5个PEP8规范。对于想要检验自己学习成果的读者,这些题目将是一个不错的选择。请注意,答案在视频中,本文不提供答案。 ... [详细]
  • OpenMap教程4 – 图层概述
    本文介绍了OpenMap教程4中关于地图图层的内容,包括将ShapeLayer添加到MapBean中的方法,OpenMap支持的图层类型以及使用BufferedLayer创建图像的MapBean。此外,还介绍了Layer背景标志的作用和OMGraphicHandlerLayer的基础层类。 ... [详细]
  • 1、概述首先和大家一起回顾一下Java消息服务,在我之前的博客《Java消息队列-JMS概述》中,我为大家分析了:然后在另一篇博客《Java消息队列-ActiveMq实战》中 ... [详细]
  • 浅谈Python3中打开文件的方式(With open)
    浅谈Python3中打开文件的方式(With open)-目录0.背景知识1.常规方式:读取文件-----open()2.推荐方式:读取文件-----WithOpen1).读取方式 ... [详细]
  • Python3怎么获取文件属性
    这篇文章给大家分享的是有关Python3怎么获取文件属性的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。os.stat(path ... [详细]
  • Python Flask学习之安装SQL,python3,Pycharm(网上下载安装即可)
    1,下载时更改pypi源。可以额外安装虚拟化环境:pipinstall-ihttp:pypi.douban.comsimple--trusted-hos ... [详细]
  • 这篇文章给大家分享的是有关python3怎样中文转换编码的内容。小编觉得挺实用的,因此分享给大家做个参考。一起跟随小编过来看看吧。示例:处理 ... [详细]
  • 1.下载git和Pycharm并安装2.打开Pycharm,点击file-->DefaultSettins-->VersionControl-->Git然后在 ... [详细]
author-avatar
mobiledu2502854077
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有