正则表达式 For Python

2017-10-07  本文已影响0人  Moscow1147
Manarola

正则表达式有很多流派,也有很多的特性,不同的语言支持度也是不一样的。本篇文章是写Python中的正则表达式的用法的,介绍了一些可用特性,也指出了某些特性是不支持的。

本篇文章仅为学习笔记,不对正则表达式做深入研究,只想用最短的时间入门正则。

实践是检验真理的唯一标准。你以为的并不是你以为的,如有问题还是要写一个例子用多个工具验证下最好。

字符组 [ ]

在同一位置可能出现的各种字符,也可以称之为字符集。

写法有很多种。有原始写法,然后是范围表示法,然后是排除型字符组,然后是字符组简记法,最后还有一个字符组运算(Python不支持)。

[3645970128]  -->  [0123456789]  -->  [0-9]  -->  \d
[0-9A-Za-z_]  -->  [\dA-Za-z_]  -->  \w
"-"用来表示范围 范围的确定依靠码值来确定,一般据ASCⅡ表值。

排除型字符组由脱字符"^"来指定,"^"排在第一位,紧靠在"["之后,需要排除的按上面写法写在"^"之后。
[^0-9]    # 排除数字,也可以写成[^\d]
如果需要排除"-",则必须将"-"写在开头"^"之后,如果一定要写在后面,则必须用"\"转义。
字符组简记 说明
\d 数字(digit) [0-9]
\w 单词(word) [0-9A-Za-z_] 字母数字下划线
\s 空白(space) [ \t\r\n\f\v] 空格、制表符、回车、换行等显示空白字符

\D 、\W 、\S是与之对应的排除型字符组简记。

以上说的\d 、\w 、\s的匹配规则都是针对ASCⅡ编码规则而言的,也叫ASCⅡ匹配规则。在Unicode编码中,全角数字0、1、2之类的也算数字,也可以由\d匹配;中文字符也可以算是单词字符,由\w匹配;同样全角空格也由\s匹配。

Python3默认采用Unicode匹配规则,但也可以显示指定采用ASCⅡ匹配规则(表达式最开始用(?a)指定ASCⅡ模式)。

另外还有一个POSIX字符组(POSIX Character Class)。POSIX(Portable Operating System Interface for uniX),它是含有正则表达式规范的一系列规范。这种字符组只在java、php、ruby中支持,经过测试以及查阅资料并没有显示Python是支持的,不过也有可能是POSIX字符组需要变化一下才能使用,就像其他语言那样。

量词

以上说的都是匹配单个字符,如果多了怎么办。所以就出现了量词。

量词限定之前的元素的出现,这个元素可能是一个字符,也可能是一个字符组,还可以是一个表达式。

一般形式量词 说明
{n} 之前的元素必须出现n次
{m,n} 之前的元素最少出现m次,最多出现n次
{m,} 之前的元素最少出现m次,最高没有限制
{0,n} 之前的元素可以不出现,也可以出现,最多出现n次
常用量词 等价形式 说明
* {0,} 可能出现,也可能不出现,出现次数没有上限
+ {1,} 至少出现1次,最多没有限制
? {0,1} 可以不出现,也可以就出现1次

所有的量词默认都是贪婪量词,能匹配的,绝不停止匹配。它会优先尝试匹配,并记下这个状态,以便将来反悔。这个返回的过程称之为回溯。

量词也可以变成懒惰的,称之为懒惰量词,能不匹配就不匹配,如果之后的表达式匹配失败,则会回溯进行匹配。变成懒惰量词是在现有量词后加上?

还有一种量词称之为占有量词。它和贪婪量词很像,但是占有量词不会进行回溯,优点是速度快,但是失败也会很快。变成占有量词是在现有量词后加上+

分组 ( )

分组,顾名思义就是将某些东西分成一组,对外来看是一个整体。通过()将表达式括起来,此时表达式就是一个整体一个可整体操作的元素。括号内的表达式称之为“子表达式”。

多选结构

多选结构的形式是(···|···),在括号中以竖线|分隔开多个子表达式,这些子表达式也叫多选分支;在一个多选结构中,多选分支的数目没有限制。在匹配时,整个多选结构被视为单个元素,只要某个子表达式能够匹配,整个多选结构的匹配就成功。

一般而言ab|cd依旧是多选结构,等价于(ab|cd),建议使用后者,更加明确,也能够避免一些错误。例如^ab|cd$其实是(^ab|cd$)而不是^(ab|cd)$,因为竖线|的优先级很低。

多选结构的匹配顺序问题。比如表达式(aaa|aaabbb)去匹配aaabbb,匹配结果到底是aaa还是aaabbb呢?Python中多选结构都会优先选择最左侧的分支,所以匹配出来是aaa。不过在实际使用中请避免这种情况,分支中存在重复会大大增加回溯的计算量。

引用分组

分组之后,正则表达式会保存每一个分组真正匹配的文本(不是正则表达式可匹配的文本),匹配完成后,可通过编号进行引用当时捕获的内容。

如果出现多个嵌套括号,那编号的计数规则是怎样的呢?首先整个正则表达式默认存在一个的编号为0的分组,其他分组的编号是依据从左到右开括号出现的顺序决定的,第几个括号就是第几组。

  1. 反向引用

    在正则表达式内部引用之前的捕获分组匹配的文本,之前捕获分组中锚点表示的位置信息不会被保留下来。

    引用形式:\num,其中num表示所引用分组的编号。

  2. sub替换中引用

    re.sub(pattern,replacement,string),在replacement中使用引用分组。引用形式同样是:\num

  3. 匹配结果中group引用

    对匹配完成后,可对匹配结果对象调用group(num)获取分组中匹配的数据。

\num引用的二义性问题:\10是第10个引用还是第1个引用后再加一个0呢?

Python中将其解释为第10个引用,可通过\g<num>,消除二义性。

注:反向引用只引用之前的捕获分组匹配的文本,之前捕获分组中的锚点表示的位置信息不会被保留下来。

命名分组

鉴于数字编号不够直观,并且括号多了难免混淆,所以就有了命名分组的出现。Python中用(?P<name>...)来分组,其中name是分组的名称。

反向引用必须使用(?P=name)来引用;替换则需要写作\g<name>;匹配结果中则使用group(name)

非捕获分组

无论是否需要分组,只要出现了括号,正则表达式再匹配的时候就会把括号内的子表达式存储起来,提供引用。如果并不需要引用,保存这些信息无疑会影响正则表达式的性能。

正则表达式提供了非捕获分组(?:...),这样的括号叫做非捕获型括号,他只能限定量词的作用范围,不捕获任何文本。在引用分组时,分组的编号同样会按照开括号出现的顺序从左到右计数,非捕获分组会略过。

原子分组

另一种非捕获分组就是原子分组(?>...)。这种分组可以将回溯操作关闭,但它只针对原子分组内的部分,而不针对整个正则表达式。经过测试发现这个表达式?>是不识别的。但是一篇文章英文原版)给出了一个等价方案:

可以通过使用零宽度先行assert((?= RE))来模拟它,它从当前点匹配相同的语义想要,将一个命名的组((?P RE))放在lookahead中,然后使用一个命名的反引用((?P = name)恰恰是零宽度断言匹配.组合在一起,这给了你相同的语义,代价是创建一个额外的匹配组和很多语法.

匹配模式

所谓匹配模式,指的是匹配时使用的规则。设定特定的模式,可能会改变对正则表达式的识别,也可能会改变正则表达式中字符的匹配规定。常用的匹配模式一共有4种:不区分大小写模式单行模式多行模式注释模式。通常有两种指定方式:以模式修饰符指定(?modifier),或者以预定义的常量作为特殊参数传入来指定。

Python中模式修饰符只要出现,无论在什么位置,都对整个正则表达式生效。Python中不支持失效修饰符(?-modifier)

不区分大小写模式

不区分大小写模式的修饰符是i (case Insensitive),则(?i)it可以匹配的有it It iT IT

单行模式

修饰符是s (Single line)。

这种模式下所有的文本只在一行里,换行符只是其中一个普通字符。单行模式影响的是点号.的匹配规则。

多行模式

修饰符是m (Multiline)。

多行模式影响的是^$的匹配规则。

注释模式

修饰符是x (eXtended mode 扩展模式)。

有时正则表达式可能非常复杂,不但难于编写和阅读,也难以维护。python中支持使用(?#comment)的记法添加注释;还有一种是使用注释模式,此时正则表达式对应的字符串可以跨越多行,注释则以#comment的形式添加在正则表达式内部,每一条注释从#开始,到行末结束,同时还可以使用缩进表示层级结构更加方便阅读和维护。

断言

常见的断言有三类:单词边界行起始/结束位置环视。断言并不真正的匹配文本,他匹配的是位置,他只负责判断在某个位置左/右侧的文本是否符合要求。

单词边界

单词边界记为\b,他匹配的是“单词边界”位置,而不是字符。也就是说\b能够匹配这样的位置:一边是单词字符,另一边不是单词字符(包括没有字符)。不同的模式下\w不同,所以\b也就不同。

另外还有\B非单词边界。

行起始/结束为位置

^匹配字符串开始的位置。

$匹配字符串结束的位置。

前面已经说了:多行模式影响的是^$的匹配规则。

在不指定多行模式的情况下:^匹配的是整个字符串的开始位置,$匹配的是整个字符串的结束位置。

当在多行模式下:^匹配的是字符串的起始位置以及换行之后行终止符之后的位置,如果字符串末尾出现了行终止符,则依旧匹配行终止符之后的那个位置;$匹配的是字符串的结束位置,但是这个位置是在字符结束之后行终止符之前的位置。

python中还有一个特殊标记\Z,它和$类似,但是他不受多行模式的影响,\Z不管行终止符,他匹配的是整个字符串的结束位置,在行终止符之后的位置。

^$的另一个特点是在进行正则表达式替换的时候并不会被替换。也就是说,在起始和结束位置进行替换,只会在起始和结束位置添加一些字符,位置本身依然存在。

环视

环视是对匹配字符周围字符的验证,他匹配的并不是字符,而是位置。

环视大致分为以下四种:

名称 又称 说明
肯定顺序环视 正前瞻 ...(?=...)
否定顺序环视 反前瞻 ...(?!...)
肯定逆序环视 正后顾 (?<=...)...
否定逆序环视 反后顾 (?<!...)...

肯定环视和否定环视的区别就是:肯定环视要想判断成功,字符串中必须有字符由环视结构中的表达式匹配;而否定环视要判断成功却有两种情况1.字符串的字符不能匹配2.根本就没有字符,这个位置可能是起始或结束位置。

环视的组合:

  1. 包含

    环视中包含环视,这个和环视的并列还不太一样,这个外层环视是限定的位置,但是这个内层环视是限定外层环视的。(?=...(?!...))

  2. 多环视并列

    环视的并列要求每一个环视对当前位置的匹配都必须成功,而各个环视之间是没有任何联系的,他们都将作用于该位置。

  3. 环视作为多选分支

    将若干个环视作为多选分支排列在多选结构中,只要有一个环视分支成立,整个多选就成立。

分组的编号只与捕获型括号相关,而不受其他任何括号类型的影响,所以环视结构的括号并不影响分组。但是环视结构中出现了捕获型括号,则会影响分组。

环视结构中的捕获型括号一旦匹配完成,就不能回溯。e.g.(?<=(\d+))\w+\1无法在123a12中找到匹配。

Python支持顺序环视,但对于逆序环视,Python只支持匹配固定长度文本表达式。(?<=dogs?)(?<=(dog|cats))都是不合法的,遇到这样的可与选择多选结构来改造,但是如果出现*和+之类的量词,就不能用多选了。

至此,整个正则表达式部分就介绍完毕,以下部分Python正则API。


Python中的正则表达式模块是re模块,通过import re 导入re模块来使用正则表达式。

在正则表达式中,大多数的函数是类似re.search(pattern,string)的形式,其中pattern通常是正则表达式的字符串,这可以视为“静态方法”。其实也可以将RegexObject对象作为pattern参数传入,或者直接对RegexObject对象调用相同名字的成员方法,结果相同,但是后面这两个方法可以显示的缓存RegexObject对象。

除了正则表达式对象RegexObject,还有一个MatchObject对象,MatchObject对象提供了一些方法和属性,通过他们可以获取匹配的信息。

方法 描述 注释
start(n) 返回编号为n的捕获分组匹配的开始位置,如果对应分组不存在或者未匹配,则返回-1 如果没有设定n,则返回整个表达式匹配的开始位置,此时n等于0
pos 返回整个表达式匹配的开始位置
end(n) 返回编号为n的捕获分组匹配的结束位置,如果对应分组不存在或未匹配,则返回-1 如果没有设定n,则返回整个表达式匹配的结束位置,此时n等价于0
endpos 返回整个表达式匹配的结束位置 等价于end(0)
group(n) 返回编号为n的捕获分组匹配的文本 如果没有设定n,则返回整个表达式匹配的文本,此时n等价于0
groups() 返回各捕获分组匹配的文本构成的tuple 如果表达式中不存在捕获分组,则返回空tuple
span(n) 返回编号为n的捕获分组匹配的开始-结束位置 如果没有设定n,则返回整个表达式匹配的开始-结束位置,此时n等价于0
re 返回匹配时所使用的正则表达式对象
lastindex 返回匹配成功的编号最大的捕获分组的编号 如果没有,则返回None
string 返回用来尝试用正则表达式来匹配的字符串
expand(str) 返回一个字符串,可以在其中引用捕获分组匹配的文本
上一篇下一篇

猜你喜欢

热点阅读