正则表达式

| 分类 regular  | 标签 regular  re  浏览次数: -

正则表达式的分类

  • 基本的正则表达式(Basic Regular Expression 又叫Basic RegEx 简称BREs)
  • 扩展的正则表达式(Extended Regular Expression 又叫Extended RegEx 简称EREs)
  • Perl的正则表达式(Perl Regular Expression 又叫Perl RegEx 简称PREs)

基本组成部分

正则表达式 描述 示例 Basic RegEx Extended RegExPython RegExPerl regEx
\ 转义符,将特殊字符进行转义,忽略其特殊意义 a.b匹配a.b,但不能匹配ajb,.被转义为特殊意义 \ \ \ \
^ 匹配行首,awk中,^则是匹配字符串的开始 ^tux匹配以tux开头的行 ^ ^ ^ ^
$ 匹配行尾,awk中,$则是匹配字符串的结尾 tux$匹配以tux结尾的行 $ $ $ $
. 匹配除换行符\n之外的任意单个字符 ab.匹配abc或abd,不可匹配abcd或abde,只能匹配单字符 . . . .
[] 匹配包含在[字符]之中的任意一个字符 coo[kl]可以匹配cook或cool [] [] [] []
[^] 匹配[^字符]之外的任意一个字符 123[^45]不可以匹配1234或1235,1236、1237都可以 [^] [^] [^] [^]
[-] 匹配[]中指定范围内的任意一个字符,要写成递增 [0-9]可以匹配1、2或3等其中任意一个数字 [-] [-] [-] [-]
? 匹配之前的项1次或者0次 colou?r可以匹配color或者colour,不能匹配colouur 不支持 ? ? ?
+ 匹配之前的项1次或者多次 sa-6+匹配sa-6、sa-666,不能匹配sa- 不支持 + + +
* 匹配之前的项0次或者多次 co*l匹配cl、col、cool、coool等 * * * *
() 匹配表达式,创建一个用于匹配的子串 ma(tri)?匹配max或maxtrix 不支持 () () ()
{n} 匹配之前的项n次,n是可以为0的正整数 [0-9]{3}匹配任意一个三位数,可以扩展为[0-9][0-9][0-9] 不支持 {n} {n} {n}
{n,} 之前的项至少需要匹配n次[0-9]{2,}匹配任意一个两位数或更多位数 不支持 {n,} {n,} {n,}  
{n,m} 指定之前的项至少匹配n次,最多匹配m次,n<=m [0-9]{2,5}匹配从两位数到五位数之间的任意一个数字 不支持 {n,m} {n,m} {n,m}
| 交替匹配|两边的任意一项 ab(c|d)匹配abc或abd 不支持 | | |

POSIX字符类

POSIX字符类是一个形如[:…:]的特殊元序列(meta sequence),他可以用于匹配特定的字符范围。

正则表达式 描述 示例 Basic RegEx Extended RegEx Python RegEx Perl regEx
[:alnum:] 匹配任意一个字母或数字字符 [[:alnum:]]+ [:alnum:] [:alnum:] [:alnum:] [:alnum:]
[:alpha:] 匹配任意一个字母字符(包括大小写字母) [[:alpha:]]{4} [:alpha:] [:alpha:] [:alpha:] [:alpha:]
[:blank:] 空格与制表符(横向和纵向) [[:blank:]]* [:blank:] [:blank:] [:blank:] [:blank:]
[:digit:] 匹配任意一个数字字符 [[:digit:]]? [:digit:] [:digit:] [:digit:] [:digit:]
[:lower:] 匹配小写字母 [[:lower:]]{5,} [:lower:] [:lower:] [:lower:] [:lower:]
[:upper:] 匹配大写字母 ([[:upper:]]+)? [:upper:] [:upper:] [:upper:] [:upper:]
[:punct:] 匹配标点符号 [[:punct:]] [:punct:] [:punct:] [:punct:] [:punct:]
[:space:] 匹配一个包括换行符、回车等在内的所有空白符 [[:space:]]+ [:space:] [:space:] [:space:] [:space:]
[:graph:] 匹配任何一个可以看得见的且可以打印的字符 [[:graph:]] [:graph:] [:graph:] [:graph:] [:graph:]
[:xdigit:] 任何一个十六进制数(即:0-9,a-f,A-F) [[:xdigit:]]+ [:xdigit:] [:xdigit:] [:xdigit:] [:xdigit:]
[:cntrl:] 任何一个控制字符(ASCII字符集中的前32个字符) [[:cntrl:]] [:cntrl:] [:cntrl:] [:cntrl:] [:cntrl:]
[:print:] 任何一个可以打印的字符 [[:print:]] [:print:] [:print:] [:print:] [:print:]

元字符

元字符(meta character)是一种Perl风格的正则表达式,只有一部分文本处理工具支持它,并不是所有的文本处理工具都支持。

正则表达式 描述 示例 Basic RegEx Extended RegEx Python RegEx Perl regEx
\b 单词边界 \bcool\b 匹配cool,不匹配coolant \b \b \b \b
\B 非单词边界 cool\B 匹配coolant,不匹配cool \B \B \B \B
\d 单个数字字符 b\db 匹配b2b,不匹配bcb 不支持 不支持 \d \d
\D 单个非数字字符b\Db 匹配bcb,不匹配b2b 不支持 不支持 \D \D  
\w 单个单词字符(字母、数字与_) \w 匹配1或a,不匹配& \w \w \w \w
\W 单个非单词字符 \W 匹配&,不匹配1或a \W \W \W \W
\n 换行符 \n 匹配一个新行 不支持 不支持 \n\ n
\s 单个空白字符 x\sx 匹配x x,不匹配xx 不支持 不支持 \s \s
\S 单个非空白字符 x\S\x 匹配xkx,不匹配xx 不支持 不支持 \S \S
\r 回车 \r 匹配回车 不支持 不支持 \r \r
\t 横向制表符 \t 匹配一个横向制表符 不支持 不支持 \t \t
\v 垂直制表符 \v 匹配一个垂直制表符 不支持 不支持 \v \v
\f 换页符 \f 匹配一个换页符 不支持 不支持 \f \f

Python re模块

re.match 尝试从字符串的开始匹配一个模式,原型为:re.match(pattern, string, flags)

  • 第一个参数是正则表达式,这里为”(\w+)\s”,如果匹配成功,则返回一个Match,否则返回一个None;
  • 第二个参数表示要匹配的字符串;
  • 第三个参数是标致位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。

re.search函数会在字符串内查找模式匹配,只到找到第一个匹配然后返回,如果字符串没有匹配,则返回None。 re.sub用于替换字符串中的匹配项,原型为:re.sub(pattern, repl, string, count)

  • 其中第二个函数是替换后的字符串;
  • 第四个参数指替换个数。默认为0,表示每个匹配项都替换。

re.sub还允许使用函数对匹配项的替换进行复杂的处理。 re.split来分割字符串 re.findall可以获取字符串中所有匹配的字符串 re.compile可以把正则表达式编译成一个正则表达式对象。可以把那些经常使用的正则表达式编译成正则表达式对象,这样可以提高一定的效率。

python正则表达式

正则表达式(regular expressiong)是用一种形式化语法描述的文本匹配模式。模式被解释为一组指令,然后会执行这组指令,以一个字符串作为输入,生成一个匹配的字迹或者原字符串的修改版本。“正则表达式”一词在讨论中通常会简写为“regex”或者“regexp”。表达式可以包括字面文字匹配、重复、模式组、分支以及其他复杂的规则。对于很多解析问题,用正则表达式解决会比创建特殊用途的词法分析器和语法分析器容易。

正则表达式通常在涉及大量文本处理的应用中使用。例如,在开发人员使用的文本编辑程序中,常用正则表达式作为搜索模式。另外,正则表达式还是UNIX命令行工具的一个不可或缺的部分。如sed、grep和awk。很多编程语言都在语法中包括对正则表达式的支持,如Perl、Ruby、Awk和TCL。另外一些语言(如C、C++和Python)通过扩展库来对正则表达式支持。Python的re模块中使用的语法以Perl所用的正则表达式语法为基础,并提供了一些特定于Python的改进。

1. 模式语法

1.1 字符集

字符集(character set)是一组字符,包含可以与模式中相应位置匹配的所有字符。例如:[ab]可以匹配a或者b。^在字符集的首位时,表否定,即不包含字符集中的字符。随着字符集的变大,可以使用一种更紧凑的模式,利用字符区间来定义一个字符集,其中包含一个起点和一个终点之间的所有连续的字符。如:[a-zA-Z0-9]表示匹配所有大小写字母和数字的字符。元字符“.”(点号)指模式应当匹配位置的任意单个字符。

1.2 转义码

还有一种更为紧凑的表示,可以对一些预定义的字符集使用转义码。re可以识别的转义码表示:

转意码 含义
\d 数字
\D 非数字
\s 空白字符(空白,tab,换行)
\S 非空白字符
\w 字母数字
\W 非字母数字

转义字符通过在该字符前面加一个反斜杠(\)前缀表示。遗憾的是,正常的Python字符串中反斜线自身也必须转义,这就会导致表达式很难阅读。通过使用“原始”(raw)字符串(在字面值前面加一个前缀r来创建),可以消除这个问题,并维持可读性。如:

pattern_list=[r'\d+', r'\D+', r'\s+', r'\w+',]

要匹配隶属于正则表达式中的字符,需要对搜索模式中的字符进行转义。如下面的例子中的模式对反斜线和加号字符进行了转义,这两个字符在正则表达式中都有特殊含义。

pattern = r'\\.\+'

1.3 锚定

锚定(anchoring)指输入文本中模式应当出现的相对位置,如下表所示:

锚定码 意义
^ 字符串开头
$ 字符串结尾
\A 字符串开始
\Z 字符串结束
\b boundary,单词边界
\B 不在单词开头或者结尾的位置
(?=pattern) 肯定顺序环视,即右边是pattern的位置
(?!pattern) 否定顺序环视,即右边不是pattern的位置
(?<=pattern) 肯定逆序环视,即左边是pattern的位置
(?<!pattern) 否定逆序环视,即左边不是pattern的位置

这里注意,在环视中匹配的字符长度必须确定,不能使用长度不确定的字符,如*+等。

pattern = r'^\w+'     # 以word结束  
pattern = r'\A\w+'    # 以word开始的字符串  
pattern = r'\w+\S*$'  # 字符串尾部的word,不包括标点符号  
pattern = r'\w+\S*\Z' # 字符串尾部的word,不包括标点符号  
pattern = '\w*t\W*'   # 含有t字母的word  
pattern = r'\bt\w'    # 以t开头的word  
pattern = r'\w+t\b'   # 以t结尾的word  
pattern = r'\Bt\B'    # t既不在开头也不在结尾的word  

1.4 选择符和子模块

如果只想匹配‘python’和‘perl’,则可以用选择运算符管道符号‘ ’,模式可写为‘python perl’
如果不需要对整个模式使用选择运算符,而只需要一部分,可以用括号括起需要的部分,对于上例,表示为‘p(ython erl)’。括号括起部分称为子模块(subpattren)  

1.5 可选项和重复子模块

在子模块后面加上问号,就变成了可选项。

  • (pattern)?:允许模式出现0次或者1次。
  • (pattern)+:允许模式出现1次或者多次。
  • (pattern)*:允许模式出现0次或者多次。
  • (pattern){m,n}:允许模式出现m~n次。

1.6 贪婪和非贪婪模式

模式中有5种表达重复的方式。如果模式后面的跟有原字符*,这个模式会被匹配0次或者多次。如果是+,那么这个模式至少出现1次。使用“?”则表示要出现0或者1次。如果希望出现特定的次数,需要在模式后面使用{m},这里的m是模式匹配需要重复的次数。最后,如果如果允许重复次数在可变范围,那么可以使用{m,n},这里的m是重复的最小次数,n是重复的最大次数。如果省略n,表示至少出现m次,且无上限。 正常情况下,处理重复指令时,re匹配模式会利用(consume)尽可能多的输入。这种所谓的“贪心”行为可能导致单个匹配减少,或者匹配中包含了多余原先预定的输入文本。在重复指令后面加上“?”可以关闭这种贪心行为。

pattern = r'\*(.+)\*'
re.sub(pattern, r'<em>\1</em>', '*This* is *it*!')
#'<em>This* is *it</em>'

可见贪婪模式匹配了开始星号到结束星号间的全部内容,包括中间两个星号。 用(.+?)代替(.+)得到非贪婪模式,它会匹配尽可能少的内容。

pattern = r'\*(.+?)\*'
re.sub(pattern, r'<em>\1</em>', '*This* is *it*!')
#'<em>This</em> is <em>it</em>'

2. re模块的函数

2.1 re.search

re常见的用法就是搜索文本中的模式。search()函数取模式和要扫描的文本为输入,如果找到这个模式,则返回Match对象,如果未找到,search()返回为None。每个Match对象包含有关匹配的信息,包括原输入字符串、使用的正则表达式,以及模式在原字符串中出现的位置。如果Match对象为M,则M.re.pattern保存的正则表达式,M.string则为匹配的字符串,M.start()和M.end()则为匹配到的首位位置。

import re
pattern = 'this'
text = 'Does this text match the pattern?'
match = re.search(pattern, text)
s = match.start()
e = match.end()
print 'Found "%s" in:\n"%s"\nfrom %d to %d ("%s")' % \
    (match.re.pattern, match.string, s, e, text[s:e])

2.2 match 对象

Match对象是一次匹配的结果,包含了很多关于此次匹配的信息,可以使用Match提供的可读属性或方法来获取这些信息。

属性:

  • string: 匹配时使用的文本
  • re: 匹配时使用的Pattern对象
  • pos: 文本中正则表达式开始搜索的索引。值与Pattern.match()和Pattern.seach()方法的同名参数相同
  • endpos: 文本中正则表达式结束搜索的索引。值与Pattern.match()和Pattern.seach()方法的同名参数相同
  • lastindex: 最后一个被捕获的分组在文本中的索引。如果没有被捕获的分组,将为None
  • lastgroup: 最后一个被捕获的分组的别名。如果这个分组没有别名或者没有被捕获的分组,将为None

方法:

  • group([group1, …]): 获得一个或多个分组截获的字符串;指定多个参数时将以元组形式返回。group1可以使用编号也可以使用别名;编号0代表整个匹配的子串;不填写参数时,返回group(0);没有截获字符串的组返回None;截获了多次的组返回最后一次截获的子串。
  • groups([default]): 以元组形式返回全部分组截获的字符串。相当于调用group(1,2,…last)。default表示没有截获字符串的组以这个值替代,默认为None。
  • groupdict([default]): 返回以有别名的组的别名为键、以该组截获的子串为值的字典,没有别名的组不包含在内。default含义同上。
  • start([group]): 返回指定的组截获的子串在string中的起始索引(子串第一个字符的索引)。group默认值为0。
  • end([group]): 返回指定的组截获的子串在string中的结束索引(子串最后一个字符的索引+1)。group默认值为0。
  • span([group]): 返回(start(group), end(group))。
  • expand(template): 将匹配到的分组代入template中然后返回。template中可以使用\id或\g、\g引用分组,但不能使用编号0。\id与\g是等价的;但\10将被认为是第10个分组,如果你想表达\1之后是字符'0',只能使用\g<1>0。

2.3 re.compile

re包含一些模块机函数,用于处理作为文本字符串的正则表达式,不过对于频繁使用的表达式,编译这些表达式更为高效。compile()函数会把一个表达式字符串转换为一个RegexObject。模块级函数会维护已编译表达式的一个缓存。不过,这个缓存的大小是有极限的,直接用已编译表达式可以避免缓存查找开销。使用已编译表达式的另一个好处是,通过在加载模块时预编译所有表达式,可以把编译工作转到应用开始时,而不是当程序响应一个用户动作时才进行编译。对于一个编译好的表达式M,M.pattern保存正则表达式。

import re  
regexes = [re.compile(p) for p in ['this', 'that']]  
text = 'Does this text match the pattern?'  
print 'Text: %r\n' % text  
for regex in regexes:  
    print 'Seeking "%s" ->' % regex.pattern,  
    if regex.search(text):  
        print 'match!'  
    else:  
        print 'no match'  

限制搜索

已编译正则表达式的search()方法还可接受可选的start和end位置参数,将搜索限制在输入的一个子串上。

import re
text = 'This is some his bist -- with punctuation.'
pattern = re.compile(r'\b\w*is\w*\b')
print "Text: ", text
print
begin = 5
end = 7
match = pattern.search(text, begin, end)
if match:
    s = match.start()
    e = match.end()
    print '  %2d :%2d = "%s"' % (s, e-1, text[s:e])

2.4多重匹配 re.findall/re.finditer

findall()函数会返回输入中与模式匹配而不重叠的所有子串。

import re
text = 'abbaaabbbaaaa'
pattern = 'ab*'
 # the result will be ['abb', 'a', 'a', 'abbb', 'a', 'a', 'a', 'a']
print re.findall(pattern, text)

finditer()会返回一个迭代器,它将生成Match实例,而不像findall()返回字符串。

import re  
text = 'abbaaabbbbaaaa'  
pattern = 'ab'  
for match in re.finditer(pattern, text):  
    s = match.start()  
    e = match.end()  
    print "Found '%s' at %d:%d" % (text[s:e], s, e)  
    print match.group()
    print match.span()

2.5 re.sub(pattern, repl, string, count=0, flags=0)

除了文本搜索以外,re还支持正则表达式作为搜索机制来修改文本,而且替换可以引用正则表达式中的匹配组作为替换文本的一部分。使用sub()可以将一个模式的所有出现替换为另一个字符,返回字符串s的一个拷贝,该串中的所有匹配均被替换成了 repl 。repl可以是一个字符串或一个可调用对象(函数或别的东西)。仅当不存在前一个匹配时,使用空的匹配(空串)替换。

  • 如果repl是一个字符串,那么里面的反斜杠\将被处理。即:\n会被解释称换行,\r被解释成回车(carriage return),后向引用也会被解析,(向后引用是一个 /g 形式的 repl 子串,这里 id 是 r 中一个分组的名字(由 r 中模式字符串中的 (?P) 建立), 或 /dd, 这里 dd 是一个或两个数字,代表一个分组的编号,如`\6`会被解释称匹配组中的第六个)每个向后引用,不论是命名的还是编号的,都被其所代码的匹配内容所替换。且它不是一个向后引用时,sub 使用 repl 自身作为替换。无法解析的反斜杠表达式如\j将不处理。
  • 若 repl 是一个可调用对象, repl 必须接受且仅接受一个 match 对象做为参数,并返回用来替换匹配的字符串。 sub 调用 repl, 并提供给 repl 一个合适的 match-object 参数, repl则为每个匹配返回一个替换值。
import re  
bold = re.compile(r'\*{2}(.*?)\*{2}')  
text = 'Make this **bold**. This **too**.'  
print 'Text:', text  
print 'Bold:',bold.sub(r'<b>\1</b>', text)  

要在替换中使用命名组,可以使用语法\g

bold2 = re.compile(r'\*{2}(?P<bold_text>.*?)\*{2}',re.UNICODE)
print 'Text:', text
print 'Bold:',bold2.sub(r'<b>\g<bold_text></b>', text)

当 count 大于 0 时,只有前 count 个匹配被替换。若 count 等于 0, 所有的匹配都被替换。

import re  
bold = re.compile(r'\*{2}(.*?)\*{2}', re.UNICODE)  
text = 'Make this **bold**. This **too**.'  
print 'Text:', text  
print 'Bold:',bold.sub(r'<b>\1</b>', text, count = 1)  

subn()的工作原理与sub()很相似,只是它会返回修改后的字符串和完成的替换次数

2.6 模式分拆 sub.split

str.split()是分解字符串完成解析的做常用方法之一。不过,它只支持使用字面值作为分隔符。有时,如果输入没有一致的格式,就需要又一个正则表达式。例如,很多纯文本标记语言都把段落分隔符定义为两个或者多个换行符(\n)。在这种情况下,就不能使用str.split(),因为这个定义中提到了“或多个”。 使用findall()标识段落有一种策略,使用类似(.+?)\n{2,}的模式。

import re
text = """Paragraph one
on two lines.

Paragraph two.


Paragraph three.

"""



for num, para in enumerate(re.findall(r'(.+?)\n{2,}',
                                      text,
                                      flags=re.DOTALL)):

    print num, repr(para)

# the result will be:
#0 'Paragraph one\non two lines.'
#1 'Paragraph two.'

可以扩展这个模式,指出段落以两个或者多个换行符结束或者以输入末尾作为结束,就能修正这个问题,但会让模式更为复杂。可以转向使用re.split()而不是re.findall(),就能自动处理边界,并保证模式更简单。

import re
text = """Paragraph one
on two lines.


Paragraph two.




Paragraph three."""

text2 = 'one1two2three3four4'
for num, para in enumerate(re.split(r'\n{2,}', text)):
    print num, repr(para)

for num, para in enumerate(re.split(r'\d+', text2)):
    print num, repr(para)

结果为:

0 'Paragraph one\non two lines.'
1 'Paragraph two.'
2 'Paragraph three.\n'
0 'one'
1 'two'
2 'three'
3 'four'
4 ''

可以将表达式包围在小括号里来定义一个组,这使得split()的工作方式更类似于str.partition(),因为它返回分隔符值以及字符串的其他部分。

import re  
text = """Paragraph one 
on two lines. 
Paragraph two. 


Paragraph three. 
"""

text2 = 'one1two2three3four4'  
for num, para in enumerate(re.split(r'(\n{2,})', text)):  
    print num, repr(para)  

for num, para in enumerate(re.split(r'(\d+)', text2)):  
    print num, repr(para)  

结果为:

0 'Paragraph one\non two lines.'
1 '\n\n'
2 'Paragraph two.'
3 '\n\n\n'
4 'Paragraph three.\n'
0 'one'
1 '1'
2 'two'
3 '2'
4 'three'
5 '3'
6 'four'
7 '4'
8 ''

如果要用圆括号但又不想分隔符在结果中,那么可以使用非捕获组(:?):

import re  
line = 'one two; three, four,  five,six,     seven'  
result1 = re.split(r'[;,\s]\s*', line)  
print result1  
result2 = re.split(r'(;|,|\s)\s*', line)  
print result2  
values = result2[::2]  
delimiters = result2[1::2]+['']  
print values  
print delimiters  
print ''.join(v+d for v,d in zip(values, delimiters))  
result3 = re.split(r'(?:;|,|\s)\s*', line)  
print result3  

结果为:

result1:
['one', 'two', 'three', 'four', 'five', 'six', 'seven']
result2:
['one', ' ', 'two', ';', 'three', ',', 'four', ',', 'five', ',', 'six', ',', 'seven']
['one', 'two', 'three', 'four', 'five', 'six', 'seven']
[' ', ';', ',', ',', ',', ',', '']
one two;three,four,five,six,seven
result3:
['one', ' ', 'two', ';', 'three', ',', 'four', ',', 'five', ',', 'six', ',', 'seven']
['one', 'two', 'three', 'four', 'five', 'six', 'seven']

2.8 re.escape( string )

这是个功能比较古怪的函数,它的作用是将字符串中的非字母和非数字(non-alphanumerics)字符,用反义字符的形式显示出来。有时候你可能希望在正则式中匹配一个字符串,不过里面含有很多re使用的符号,你要一个一个的修改写法实在有点麻烦,你可以使用这个函数,例 在目标字符串s中匹配’(*+?)’这个子字符串:

>>> s= ‘111 222 (*+?) 333’
>>> rule= re.escape( r’(*+?)’ )
>>> print rule
/(/*/+/?/)
>>> re.findall( rule , s )
['(*+?)']
import re
template = "Hello [first_name] [last_name], \
    Thank you for purchasing [product_name] from [store_name]. \
    The total cost of your purchase was [product_price] plus [ship_price] for shipping. \
    You can expect your product to arrive in [ship_days_min] to [ship_days_max] business days. \
    Sincerely, \
    [store_manager_name]"

# assume dic has all the replacement data
# such as dic['first_name'] dic['product_price'] etc...
dic = {
    "first_name": "John",
    "last_name": "Doe",
    "product_name": "iphone",
    "store_name": "Walkers",
    "product_price": "$500",
    "ship_price": "$10",
    "ship_days_min": "1",
    "ship_days_max": "5",
    "store_manager_name": "DoeJohn"
}

def multiple_replace(dic, text):
    a = map(lambda key: re.escape("[" + key + "]"), dic.keys())  #add the backslash so that re can not translate string to other meaning
    pattern = "|".join(a)   # first ,generate the pattern
    repl = lambda m: dic[m.group()[1:-1]]   # repl is a function, m is a match type, return the value in dic, which key is matched 
    return re.sub(pattern, repl, text)
multiple_replace(dic, template)

3. 使用组

3.1 匹配组

为模式增加组(group)可以隔离匹配文本的各个部分,近一步可以扩展这些功能来创建一个解析工具。通过将模式包围在小括号中来分组。

pattern = 'a(ab)'    # a 后面跟着ab  
pattern = 'a(a*b*)'  # a 后面跟着 0-n 个a 和 0-n 个 b  
pattern = 'a(ab)*'  # a 后面跟着 0-n 个ab  
pattern = 'a(ab)+'  # a 后面跟着 1-n 个 ab  

任何完整的表达式都可以转换为组,并嵌套在一个更大的表达式中。所有重复修饰符可以应用到整个组作为一个整体,这就要求重复整个组模式。要访问一个模式中单个组匹配的子串,可以使用Match对象的group()方法。Match.grups()会按照表达式中与字符串匹配的顺序返回一个字符串序列。

import re  
text = "This is some text -- with punctuation."  
print text  
print  
patterns = [  
        (r'^(\w+)', 'word at start of string'),  
        (r'(\w+)\S*$', 'word at end, with optional punctuation'),  
        (r'(\bt\w+)\W+(\w+)', 'word starting with t, another word'),  
        (r'(\w+t)\b','word ending with t'),  
        ]  

for pattern,desc in patterns:  
    regex = re.compile(pattern)  
    match = regex.search(text)  
    print 'Pattern %r (%s)' % (pattern, desc)  
    print '  ', match.groups()  
    print  

结果为

This is some text -- with punctuation.
Pattern '^(\\w+)' (word at start of string)
   ('This',)
Pattern '(\\w+)\\S*$' (word at end, with optional punctuation)
   ('punctuation',)
Pattern '(\\bt\\w+)\\W+(\\w+)' (word starting with t, another word)
   ('text', 'with')
Pattern '(\\w+t)\\b' (word ending with t)
   ('text',)

3.2 匹配单个组(Match.group(n))

使用group()可以得到某个组的匹配。如果使用分组来查找字符串的各部分,不过结果中并不需要某些与组匹配的部分,此时group()会很有用。

import re  
text = 'This is some text -- with punctuation'  
print 'Input text            :', text  

# word starting with 't' then anoter word  
regex = re.compile(r'(\bt\w+)\W+(\w+)')  
print 'Pattern               :', regex.pattern  
match = regex.search(text)  
print 'Entir match           :',match.group(0)  
print 'Word starting with "t":', match.group(1)  
print 'Word after "t" word   :', match.group(2)  

其中match.group(0)表示整个匹配,match.group(1)表示第一个组,即(\bt\w+),而match.group(2)当然就表示第二个组,即:(\w+),依次类推,结果为:

Input text            : This is some text -- with punctuation
Pattern               : (\bt\w+)\W+(\w+)
Entir match           : text -- with
Word starting with "t": text
Word after "t" word   : with

3.3 命名组

python对基本分组语法做了扩展,增加了命名组。通过使用名字来指示组,这样以后就可以更容易地修改模式,而不必同时修改使用了匹配结果的代码。要设置一个组的名字,可以使用以下语法:(?P<name>pattern),要获得命名组可以用(?P=name)

import re  
text = 'Text is some text -- with punctuation.'  
print text  
print  

for pattern in [  
        r'^(?P<first_word>\w+)',  
        r'(?P<last_word>\w+)\S*$',  
        r'(?P<t_word>\bt\w+)\W+(?P<other_word>\w+)',  
        r'(?P<ends_with_t>\w+t)\b',  
        ]:  
    regex = re.compile(pattern)  
    match = regex.search(text)  
    print 'Matching "%s"' % pattern  
    print '  ', match.groups()  
    print '  ', match.groupdict()  
    print  

使用groupdict()可以获得一个字典,它将组名映射到匹配的子串。groups()返回的有序序列还包括命名模式。结果为:

Text is some text -- with punctuation.
Matching "^(?P<first_word>\w+)"
   ('Text',)
   {'first_word': 'Text'}
   
Matching "(?P<last_word>\w+)\S*$"
   ('punctuation',)
   {'last_word': 'punctuation'}

Matching "(?P<t_word>\bt\w+)\W+(?P<other_word>\w+)"
   ('text', 'with')
   {'other_word': 'with', 't_word': 'text'}

Matching "(?P<ends_with_t>\w+t)\b"
   ('Text',)
   {'ends_with_t': 'Text'}

3.4 反向引用

对于捕获组和非捕获组,还可以用反向引用。如匹配开头的连续两个相同的单词:对于非命名组:^(\w+)\W(\1),对于命名组:^(?<word>\w+)\W(?P=word) 候选模式((patter1)|(pattern2)) 组对于制定候选模式也很有用。可以使用管道符号(|)指示应当匹配某一个或者另一个模式。不过要仔细考虑管道的放置位置。如((a+) |(b+))可以匹配1-n个连续的a字符串或者1-n个连续的b字符串,而(a|b)+则表示匹配的字符串中每个字符要么是a要么是b。

3.5 非捕获组((?:pattern))

我们知道在匹配单个组中,得到每个组的匹配字符串是M.group(1)M.group(2)等等,如匹配字符”8000¥“时,分别要得到”8000“和”¥“,则模式可以为:(\d+)(¥),用模式M.group(1)得到”8000“,用M.group(2)得到”¥“。但是如果是字符串”8000.56¥“,现在只想得到整数部分”8000“和”¥“,而又只想改变正则不想改变输出M.group(1)M.group(2),这就要创建一个非捕获组,将字符串中的小数点和小数部分加到非捕获组中即可,非捕获组的语法是(?:pattern),表示仅用于分组,不提取文本。

pattern = r'(\d+)(?:\.?)(?:\d+)([¥$])$'  

这样还是可以用M.group(1)M.group(2)作为输出,结果同样也是”8000“和”¥“

4 搜索选项

利用选项标志可以改变匹配引擎处理表达式的方式。可以使用或(OR)操作结合这些标志,然后传递至compile()、search()、match()以及其他可以接受匹配模式完成搜索的函数。

4.1不分区大小写(re.IGNORECASE)

IGNORECASE使模式中的字面量字符和字符区间与大小写字符都匹配。

with_case = re.compile(pattern,re.IGNORECASE) 

4.2 DOTALL:可以跨行

DOTALL标志可以让.匹配到换行符号 DOTALL也是一个与多行文本有关的标志,正常情况下,点字符(.)可以与输入文本中除了换行符之外的所有其他字符匹配。这个标志则允许点字符还可以匹配换行符。

import re  
text = 'This is some text -- with punctuation.\nA second line.'  
pattern = r'.+'  
no_newlines = re.compile(pattern)  
dotall = re.compile(pattern, re.DOTALL)  

print 'Text:\n    %r'% text  
print 'Pattern:\n    %r' % pattern  
print 'No newlines :'  
for match in no_newlines.findall(text):  
    print '    %r' % match  

print 'Dotall      :'  
for match in dotall.findall(text):  
    print '    %r' % match  

如果没有这个标志,输入文本会与模式单独匹配。增加了这个标志后,则会利用整个字符串。

Text:
    'This is some text -- with punctuation.\nA second line.'
Pattern:
    '.+'
No newlines :
    'This is some text -- with punctuation.'
    'A second line.'
Dotall      :
    'This is some text -- with punctuation.\nA second line.'

4.3 多行输入(re.MULTILINE)

有两个标志会影响如何在多行输入中进行搜索:MULTILINEDOTALLMULTILINE标志会控制模式匹配代码如何对包含换行符的文本处理锚定指令。当打开多行模式,除了整个字符串外,还要在每一行的开头和结尾应用^\$的锚定规则。

import re  
text = 'This is some text -- with puncturation.\nA second line.'  
pattern = r'(^\w+)|(\w+\S*$)'  
single_line = re.compile(pattern)  
multiline = re.compile(pattern, re.MULTILINE)  

print 'Text:\n %r' % text  
print 'Pattern:\n  %s' % pattern  
print 'Single Line:'  

for match in single_line.findall(text):  
    print ' %r' % (match,)  

print 'MULTILINE :'  
for match in multiline.findall(text):  
    print '  %r' % (match,)  

MULTILINE会将”\n”解释为一个换行,而默认情况下只将其解释为一个空白字符。执行结果为:

Text: 'This is some text -- with puncturation.\nA second line.'
Pattern:
  (^\w+)|(\w+\S*$)
Single Line:
 ('This', '')
 ('', 'line.')
MULTILINE :
  ('This', '')
  ('', 'puncturation.')
  ('A', '')
  ('', 'line.')

4.4 Unicode

在Python2中,str对象使用的是ASCII字符集,而且正则表达式会处理假设模式和输入文本都是ASCII字符。之前描述的转义码就默认使用ASCII来定义。这些假设意味着模式\w+会匹配单词person而不会匹配单词pérson

4.5详细表达式(re.VERBOSE)

随着表达式变得越来越复杂,紧凑格式的正则表达式语法可能会成为障碍。随着表达式中组数的增加,需要做更多的工作来明确为什么需要各个元素以及表达式的各个部分究竟如何交互。使用组名有助于缓解这些问题,不过一种更好的方法是使用详细表达式,它允许在模式中嵌入注释和额外的空白符。 例如,可以用来验证Email地址的模式来说明详细模式能够更容易地处理正则表达式。

import re  
address = re.compile(  
        """ 
        # 命名组name,其中可能包含'.' 
        # for title abbreviations and middle initials. 
        ( 
        (?P<name>([\w.,]+\s+)*[\w.,]+) 
        \s* 
        # Email addresses are wrapped in angle 
        # 括号: <> 仅当name组被找到时才可匹配, 
        # 因此将前括号保存在该组中 
        < 
        )? # 前面的名字可有可无 

        # 匹配邮件地址: username@demain.tld 
        (?P<email> 
        [\w\d.+-]+ # username 
        @ 
        ([\w\d.]+\.)+ # 邮箱域名前缀 
        (com|org|deu) # 限制邮件结束域名后缀 
        ) 
        >? # 结束括号 
        """,  
        re.UNICODE | re.VERBOSE  
      )  

candidates = [  
        u'first.last@example.com',  
        u'first.last+category@gmail.com',  
        u'valid-address@mail.example.com',  
        u'not-valid@example.foo',  
        u'First Last <first.last@example.com>',  
        u'No Brackets first.last@example.com',  
        u'First Last',  
        u'First Middle Last <first.last@example.com>',  
        u'Fist M. Last <first.last@example.com>',  
        u'<first.last@example.com>',]  

  
for cd in candidates:  
    print 'Candidate:', cd  
    match = address.search(cd)  
    if match:  
        print '    Name:', match.groupdict()['name']  
        print '    Email:', match.groupdict()['email']  
    else:  
        print '    No match'  

类似于其他编程语言,能够在详细正则表达式中插入注释有利于增强可读性和可维护性。执行结果为:

Candidate: first.last@example.com
    Name: None
    Email: first.last@example.com
Candidate: first.last+category@gmail.com
    Name: None
    Email: first.last+category@gmail.com
Candidate: valid-address@mail.example.com
    Name: None
    Email: valid-address@mail.example.com
Candidate: not-valid@example.foo
    No match
Candidate: First Last <first.last@example.com>
    Name: First Last
    Email: first.last@example.com
Candidate: No Brackets first.last@example.com
    Name: None
    Email: first.last@example.com
Candidate: First Last
    No match
Candidate: First Middle Last <first.last@example.com>
    Name: First Middle Last
    Email: first.last@example.com
Candidate: Fist M. Last <first.last@example.com>
    Name: Fist M. Last
    Email: first.last@example.com
Candidate: <first.last@example.com>
    Name: None
    Email: first.last@example.com

4.5 模式中嵌入标志

如果在编译表达式时不能增加标志,如将模式作为参数传入一个将在以后编译该模式的库函数时,可以把标志嵌入到表达式字符串本身。例如,要启用不区分大小写匹配,可以在表达式开头增加(?i),如下例的pattern会匹配已T或者t开头的单词:

pattern =r' (?i)\bT\w+'

标志 缩写
IGNORECASE i
MULTLINE m
DOTALL s
UNICODE u
VERBOSE x

可以把嵌入标志放在同一组中结合使用。例如,(?imu)会打开相应的选项,支持多行Unicode字符不区分大小写的匹配。

5 自引用表达式

匹配的值还可以用在表达式后面的部分。例如,前面的Email例子可以更新为由人名和姓组成的地址,为此要包含这组的反向引用,要达到这个目的,最容易地办法是使用\num按id编号引用先前匹配的组。

address =  re.compile(  
        r""" 
        #匹配姓名 name 
        (\w+) # first name 
        \s+ 
        (([\w.]+)\s+)? # optional middle name or initial 
        (\w+) # last name 
        \s+ 
        < 
        # 邮箱地址: first_name.last_name@domain.tld 
        (?P<email> 
        \1  # first name 
        \. 
        \4 # last name 
        @ 
        ([\w\d.]+\.)+ 
        (com|org|edu) 
        )>
        """,  
        re.UNICODE | re.VERBOSE | re.IGNORECASE  
        )  

尽管这个方法很简单,不过数字id创建反向引用有两个缺点。从实用角度讲,当表达式改变时,这个组就得重新编号,每个引用可能都需要更新。另一个缺点是,采用这种方法只能创建99个引用,因为如果id编号为3位,就会解释为一个八进制字符值而不是一个引用。另一方面,如果一个表达式能超过99个组,还会产生更严重的维护问题。Python的表达式解析器包括一个扩展,可以使用(?P=name)指示表达式中先前匹配的一个命名组的值。

address =  re.compile(  
        r""" 
        # The regular name 
        (?P<first_name>\w+) # first name 
        \s+ 
        (([\w.]+)\s+)? # optional middle name or initial 
        (?P<last_name>\w+) # last name 
        \s+ 
        < 
        # The address: first_name.last_name@domain.tld 
        (?P<email> 
         (?P=first_name) # first name 
        \. 
         (?P=last_name)# last name 
        @ 
        ([\w\d.]+\.)+ 
        (com|org|edu) 
        ) > 
        """,  
        re.UNICODE | re.VERBOSE | re.IGNORECASE  
        ) 

在表达式中使用反向引用还有另外一种机制,即根据前一组是否匹配来选择不同的模式。可以修正这个Email模式,使得如果出现名字就需要匹配尖括号,如果只有Email本身就不需要尖括号。查看一个组是否匹配的语法是(?(id)yes-expressiong|no-expression),这里id是组名或者编号,yes-expression是组有值时使用的模式,no-expression则是组没有值时使用的模式。


address =  re.compile(  
        r""" 
        ^ 
        # 首先匹配姓名,可能包含 "." 
        (?P<name> 
            ([\w.]+\s+)*[\w.]+ 
        )? 
        \s* #0-n个空白 
        # 仅当name组匹配成功时,采用非捕获的方式匹配括号 
        (?(name) 
        # 采用非捕获肯定顺序环视<.*> 
        # 并命名为bracket组 
        (?P<brackets> 
        (?=(<.*>$)) 
        ) 
        | 
        # 若name组没有匹配成功 
        # 则后面不能跟<且结尾不能是>,同样采用非捕获组的方式 
        (?=([^<].*[^>]$)) 
        ) 

        # 如果brackets组匹配成功,则开始匹配<,否则匹配空白符 
        (?(brackets)< | \s*) 
        # 匹配email地址: username@domain.tld 
        (?P<email> 
        [\w\d.+-]+  # username 
        @ 
        ([\w\d.]+\.)+  #邮箱域名前缀 
        (com | org | edu)  #邮箱域名后缀 
        ) 

        # 如果brackets组匹配成功,则开始匹配>,否则匹配空白符 
        (?(brackets)>|\s*) 
        $ 
        ) 
        """,  
        re.UNICODE | re.VERBOSE | re.IGNORECASE  
        )  

这个版本的Email地址解析用了两个测试。如果name组匹配,则当前断言要求两个尖括号都出现,并建立brackets组。如果name不匹配,这个断言则会要求余下的文本不能用尖括号括起来。接下来,如果设置了brackets组,具体的模式匹配代码会借助字面量模式利用输入中的尖括号;否则,它会利用所有空格。


上一篇 pycharm快捷键     下一篇 前端笔记
目录导航