15ch

15.1引言

核心笔记:当我们讨论与字符串中模式有关的正则表达式时,我们会用术语“匹配”matching,指的是模式匹配pattern-mathcing,在Python专门术语中,有两种主要方式完成模式匹配:搜索searching和匹配matching。搜索指的是在字符串任意部分中搜索匹配的模式,匹配指的是判断一个字符串能否从起始处全部或部分匹配某个模式。搜索通过search()函数或方法来实现,而匹配是以调用match()函数或方法来实现。

15.2正则表达式使用的特殊符号和字符

首先介绍最常用的元字符(metacharacter)---特殊字符和符号。

记号 说明 正则表达式样例
literal 匹配字符串的值 foo
re1|re2 匹配字符串re1或re2 foo|bar
. 匹配任何字符(换行符除外 b.b
^ 匹配字符串的开始 ^Dear
$ 匹配字符串的结尾 /bin/*sh$
* 匹配前面出现的正则表达式零次或多次 [A-Za-z0-9]*
+ 匹配前面出现的正则表达式一次或多次 [a-z]+\.com
? 匹配前面出现的正则表达式零次或一次 goo?
{N} 匹配前面重复出现N次的正则表达式 [0-9]{3}
{M,N} 匹配前面重复出现M到N次的正则表达式 [0-9]{5,9}
[...] 匹配字符组里出现的任意一个字符 [aeiou]
[..x-y..] 匹配从字符x到y中的任意一个字符 [0-9],[A-Za-z]
[^...] 不匹配此字符集中出现的任何一个字符,或某一范围内的字符 [^aeiou],[^A-Za-z0-9]
(*|+|?|{})? 用于将之前的符号改成?版本的 .*?[a-z]
(...) 匹配封闭括号中正则表达式,并保存为子组 ([0-9]{3})?,f(oo|u)bar
\d 匹配任何数字,和[0-9]一样(\D是\d的反义,匹配任何非数字字符) data\d+.txt
\w 匹配任何数字字母字符,和[A-Za-z0-9]相同(\W是\w的反义 [A-Za-z]\w+
\s 匹配任何空白符,和[\n\t\r\v\f]相同(\S是\s的反义 of\sthe
\b 匹配单词边界(\B是\b的反义 \bThe\b
\nn 匹配已保存的子组(参考(...)) price:\16
\c 逐一匹配特殊字符c(即,消除它的特殊含义,按字面匹配 \., \\, \*
\A(\Z) 匹配字符串的起始 \ADear

15.2.1 匹配多个正则表达式

使用| 来表示“或”操作,有时也称之为联合(union)或者逻辑或(OR)

15.2.2 匹配任意单一字符

句点符号可以匹配除了换行符(NEWLINE)之外的任意一个单个字符(Python的正则表达式有一个编译标识可以去除这一限制,让句点符号可以匹配换行符)

PS:使用\. 可以匹配句点本身

15.2.3 从字符串边界开始匹配

脱字符号(^,caret)或\A用来匹配以特定字符串开头的字符串,$和\Z用来匹配特定字符串结尾

正则表达式模式 匹配的字符串
^From 匹配任何以From开始的字符串
/bin/tcsh$ 匹配任何以/bin/tcsh结束的字符串
^Subject:hi$ 匹配仅由Subject:hi组成的字符串

特别说明,如果你想匹配这两个字符中的任何一个(或全部),就必须用反斜线进行转义。例如,如果你想匹配任何以美元符号$结尾的字符串,一个可行的方法是使用正则表达式".*\$$"。

\b匹配的是单词(匹配单词与匹配字符串的不同之处便是\b\b与^$的不同之处)开头,\B是\b的反义

正则表达式模式 匹配的单词
the 任何包含有"the"的单词
\bthe 任何以"the"开头的单词
\bthe\b 仅仅匹配单词the
\Bthe 任意包含"the"但不以“the"开头的单词

15.2.4 创建字符类[]

[cr][23]用以匹配c2,c3,r2,r3,当[]中只有一个单词时,效果和|是一样的,比如[a][b]和a|b是等价的。

15.2.5指定范围(-)和否定(^)

正则表达式模式 匹配的字符
z.[0-9] 字符z+任意一个字符+一个数字
[r-u][env-x][us] "r","s","t","u"中一个字符+“e”,"n","v","w","x"中一个字符+"u"或"s"
[^aeiou] 一个非元音字符
[^\t\n] 除TAB制表符和换行符以外的一个字符
["-a] 在ASCII字符集中顺序在“到a之间的一个字符

15.2.6使用闭包操作符(*,+,?,{})实现多次出现/重复匹配

零次或零次以上的情况(在计算机语言和编译器原理里,此操作符被叫做 Kleene 闭包操作符)。加号(+)操作符匹配它左边那个正则表达式模式至少出现一次的情况(它也被称为正闭包操作符),而问号操作符( ? )匹配它左边那个正则表达式模式出现零次或一次的情况。 还有花括号操作符({ }), 花括号里可以是单个的值,也可以是由逗号分开的一对值。如果是一个值,如,{N},则表示匹配 N 次出现;如果是一对值,即,{M, N},就表示匹配 M 次到 N 次出现。

问号有两种含义:1.单独使用时表示匹配出现零次或一次的情况,2.紧跟在表示重复的元字符后面时,表示要求搜索引擎匹配的字符串越短越好。

正则表达式的模式 匹配的字符
[dn]ot? 字符"d"或"n", 后面是一个"o", 最后是最多一个字符"t",即,do, no, dot,not
0?[1-9] 从1到9中的任意一位数字,前面可能还有一个"0". 例如:可以把它看
成一月到九月的数字表示形式,不管是一位数字还是两位数字的表示形
式。
[0-9]{15,16} 15 或 16 位数字表示,例如:信用卡号码
</?[^>]+> 匹配所有合法(和无效的)HTML 标签的字符串
[KQRBNP][a-h][1-8]-[a-h][1-8] 在“长代数”记谱法中,表示的国际象棋合法的棋盘。
即, “K,” “Q,” “R,” “B,” “N,” 或 “P” 等
字母后面加上两个用连字符连在一起的"a1"到"h8"之间
的棋盘坐标。前面的编号表示从哪里开始走棋,后面的
编号代表走到哪个位置(棋格)去。

15.2.7特殊字符表示,字符集

正则表达式的模式 匹配的字符
\w+-\d+  一个由字母或数字组成的字符串,和至少一个数字,两部分中间由连
字符连接
[A-Za-z]\w* 第一个字符是字母,其余字符(如果存在的话),是字母或数字(它几乎
等价于 Python 语言中合法的标识符[见参考练习]
\d{3}-\d{3}-\d{4} (美国)电话号码,前面带区号前缀,例如 800-555-1212
\w+@\w+\.com 简单的 XXX@YYY.com 格式的电子邮件地址

15.2.8用圆括号创建组

一对圆括号(()) 和正则表达式一起使用时可以实现以下任意一个(或两个)功能: 1.对正则表达式进行分组  2.匹配子组

正则表达式模式 匹配的字符
\d+(\.\d*)? 表示简单的浮点数,即, 任意个十进制数字,后面跟一个可选的小数点,然后再接零或多个十进制数字。例如:“0.004,” “2,” “75.”,等等。
(Mr?s?\. )?[A-Z][a-z]* [A-Za-z-]+ 名字和姓氏,对名字的限制(首字母大写,其它字
母(如果存在)小写), 全名前有可选的称谓(“Mr.,”
“Mrs.,” “Ms.,” 或 “M.,”),姓氏没有什么限制,允许有多个单词、横线、大写字母。

 15.3正则表达式和Python语言

5.3.1 re模块:核心函数和方法

compile(pattern,flags=0)                对正则表达式模式pattern 进行编译,flags 是可选标志符,并返回一个regex 对象


match(pattern,string, flags=0)        尝试用正则表达式模式pattern 匹配字符串string,

                                                     flags 是可选标志符,如果匹配成功,则返回一个匹配对象;否则返回None


search(pattern,string, flags=0)         在字符串string 中查找正则表达式模式pattern 的第
                                                    一次出现,flags 是可选标志符,如果匹配成功,则返回
                                                    一个匹配对象;否则返回None


findall(pattern,string[,flags])               在字符串string 中查找正则表达式模式pattern 的所有
                                                    (非重复)出现;返回一个匹配对象的列表


finditer(pattern,string[, flags])         和findall()相同,但返回的不是列表而是迭代器;对
                                                   于每个匹配,该迭代器返回一个匹配对象


split(pattern,string, max=0)             根据正则表达式pattern 中的分隔符把字符string 分割
                                                    为一个列表,返回成功匹配的列表,最多分割max 次(默
                                                    认是分割所有匹配的地方)。


sub(pattern, repl, string, max=0)         把字符串string 中所有匹配正则表达式pattern 的地
                                                    方替换成字符串repl,如果max 的值没有给出,则对所有
                                                    匹配的地方进行替换(另外,请参考subn(),它还会返回
                                                    一个表示替换次数的数值)。


group(num=0)                                 返回全部匹配对象(或指定编号是num 的子组)


groups()                                         返回一个包含全部匹配的子组的元组

15.3.2使用compile()编译正则表达式

大多数re模块函数都可以作为regex对象的方法。编译rex对象时给出一些可选标识符,可以得到特殊的编译对象,详情参阅相关文档。这些标识符也可以传给模块版本的match()和search()进行特定模式的匹配。

15.3.3 匹配对象和group(), groups()方法

在处理正则表达式时,除regex对象外,还有另一种对象类型---匹配对象。这些对象是在match()或search()被成功调用之后所返回的结果。匹配对象有两个主要方法:group()和groups()。

group()方法或者返回所有匹配对象或是根据要求返回某个特定子组。groups()则很简单,它返回一个包含唯一或所有子组的元组。如果正则表达式中没有子组的话, groups() 将返回一个空元组,而group()仍会返回全部匹配对象。

15.3.4用match()匹配字符串

>>>m = re.match('foo','bar')

>>>if m is not None:m.group()

匹配失败,返回None

如果使用>>>re.match('foo', 'bar on the table').group()

则会引发一个AttributeError,因为match返回的None没有group属性

15.3.5 search()在一个字符串中查找一个模式(搜索和匹配的比较)

>>>m = re.match('foo','seafood')

>>>if m is not None:m.group()

会导致失败,这时改用re.search可以成功匹配到foo

15.3.6匹配多个字符串()

>>>bt = 'bat|bet|bit'

>>>m = re.match(bt, 'bat')

>>>if m is not None:m.group()

'bat'

15.3.7匹配任意单个字符

>>> patt314 = '3.14'  # RE dot   #正则表达式点号
>>> pi_patt = '3\.14'  # literal dot (dec. point)   #浮点(小数点)
>>> m = re.match(pi_patt, '3.14') # exact match   #完全匹配
>>> if m is not None: m.group()

15.3.8 创建字符集合( [ ] )

>>> m = re.match('[cr][23][dp][o2]', 'c2do')# matches 'c2do'  #匹配'c2do'
>>> if m is not None: m.group()
...
'c2do'
>>> m = re.match('r2d2|c3po', 'c2do')# does not match 'c2do'  #不匹配'c2do'
>>> if m is not None: m.group()
...

15.3.9重复、特殊字符和子组

 我们通过括号划分子组,在之前匹配邮箱地址的正则表达式中,我们可以稍作改变让其支持完整的域名

>>> patt = '\w+@(\w+\.)?\w+\.com'
>>> re.match(patt, 'nobody@xxx.com').group()
'nobody@xxx.com'
>>> re.match(patt, 'nobody@www.xxx.com').group()
'nobody@www.xxx.com'

或改成匹配任意数量的子域名

>>> patt = '\w+@(\w+\.)*\w+\.com'
>>> re.match(patt, 'nobody@www.xxx.yyy.zzz.com').group()
'nobody@www.xxx.yyy.zzz.com'

用括号匹配并保存子组的好处是你可以简单的提取各个部分

>>> m = re.match('(\w\w\w)-(\d\d\d)', 'abc-123')
>>> m.group()  # entire match 所有匹配部分
'abc-123'
>>> m.group(1)  # subgroup 1 匹配的子组 1
'abc'
>>> m.group(2)  # subgroup 2 匹配的子组 2
'123'
>>> m.groups()  # all subgroups 所有匹配子组
('abc', '123')

groups显示的是子组,所以没有使用括号时,groups会返回一个空的元组

>>> m = re.match('ab', 'ab')  # no subgroups  #无子组
>>> m.group()  # entire match
'ab'
>>> m.groups()  # all subgroups    #所有匹配的子组
()
>>>
>>> m = re.match('(ab)', 'ab')  # one subgroup #一个子组
>>> m.group()  # entire match #所有匹配
'ab'
>>> m.group(1)  # subgroup 1    #匹配的子组 1
'ab'
>>> m.groups()  # all subgroups    #所有匹配子组
('ab')
>>>
>>> m = re.match('(a)(b)', 'ab') # two subgroups  #两个子组
>>> m.group()  # entire match
'ab'
>>> m.group(1)  # subgroup 1 匹配的子组 1
'a'
>>> m.group(2)  # subgroup 2 匹配的子组 2
'b'
>>> m.groups()  # all subgroups 所有匹配子组的元组
('a', 'b')
>>>
>>> m = re.match('(a(b))', 'ab') # two subgroups   #两个子组
>>> m.group()  # entire match    #所有匹配部分
'ab'
>>> m.group(1)  # subgroup 1    #匹配的子组 1
'ab'
>>> m.group(2)  # subgroup 2    #匹配的子组 2
'b'
>>> m.groups()  # all subgroups    #所有匹配的子组的元组
('ab', 'b')

15.3.10从字符串的开头或结尾匹配及在单词边界上匹配

下面的例子强调了锚点性(positioncal)正则表达式操作符。这些锚点性正则表达式操作符主要被用于搜索而
不是匹配,因为 match()总是从字符串的开头进行匹配的。
>>> m = re.search('^The', 'The end.')  # match     #在字符串开头
>>> if m is not None: m.group()
...
'The'
>>> m = re.search('^The', 'end. The')  # not at beginning #不在字符串开头
>>> if m is not None: m.group()
...
>>> m = re.search(r'\bthe', 'bite the dog') # at a boundary #在单词开头
>>> if m is not None: m.group()
...
'the'
>>> m = re.search(r'\bthe', 'bitethe dog') # no boundary   #不在单词开头
>>> if m is not None: m.group()
...
>>> m = re.search(r'\Bthe', 'bitethe dog') # no boundary   #不在单词开头
>>> if m is not None: m.group()
...
'the'
你可能在这里注意到了原始字符串(raw strings)的出现。在本章末尾的核心笔记中,有关于
它的说明。通常,在正则表达式中使用原始字符串是个好主意。
你还应该了解另外四个 re 模块函数和 regex 对象方法: findall(), sub(), subn() 和 split().

15.3.11用findall()找到每个出现的匹配部分

findall()用于非重叠地查找某字符串中一个正则表达式模式出
现的情况。findall()和 search()相似之处在于二者都执行字符串搜索,但 findall()和 match()与
search()不同之处是,findall()总返回一个列表。如果 findall()没有找到匹配的部分,会返回空
列表;如果成功找到匹配部分,则返回所有匹配部分的列表(按从左到右出现的顺序排列)。
>>> re.findall('car', 'car')
['car']
>>> re.findall('car', 'scary')
['car']
>>> re.findall('car', 'carry the barcardi to the car')
['car', 'car', 'car']
包含子组的搜索会返回更复杂的一个列表,这样做是有意义的,因为子组是允许你从单个正则
表达式中抽取特定模式的一种机制,比如,匹配一个完整电话号码中的一部分(例如,区号),或完
整电子邮件地址的一部分(例如,登录名)。
正则表达式仅有一个子组时,findall()返回子组匹配的字符串组成的列表;如果表达式有多个
子组,返回的结果是一个元组的列表,元组中每个元素都是一个子组的匹配内容,像这样的元组(每
一个成功的匹配对应一个元组)构成了返回列表中的元素。

15.3.12用sub()(和subn())进行搜索和替换

有两种函数/方法用于完成搜索和代替的功能: sub()和 subn(). 二者几乎是一样的,都是将某
字符串中所有匹配正则表达式模式的部分进行替换。用来替换的部分通常是一个字符串,但也可能
是一个函数,该函数返回一个用来替换的字符串。subn()和 sub()一样,但它还返回一个表示替换次
数的数字,替换后的字符串和表示替换次数的数字作为一个元组的元素返回。
>>> re.sub('X', 'Mr. Smith', 'attn: X\n\nDear X,\n')
'attn: Mr. Smith\012\012Dear Mr. Smith,\012'
>>>
>>> re.subn('X', 'Mr. Smith', 'attn: X\n\nDear X,\n')
('attn: Mr. Smith\012\012Dear Mr. Smith,\012', 2)
>>>
>>> print re.sub('X', 'Mr. Smith', 'attn: X\n\nDear X,\n')
attn: Mr. Smith
Dear Mr. Smith,
>>> re.sub('[ae]', 'X', 'abcdef')
'XbcdXf'
>>> re.subn('[ae]', 'X', 'abcdef')
('XbcdXf', 2)

15.3.13 用 split()分割(分隔模式)

 re的split与string的相似,但是前者可根据正则表达式模式分割,还可以通过设定参数来指定分割的最大次数。当没使用匹配模式的时候re.split()和string.split()的执行过程是一样的,见以下例子:

>>> re.split(':'. 'str1:str2:str3')

['str1', 'str2', 'str3']

另一个例子是处理Unix下who命令输出的已登录用户的信息。(略)

核心笔记:Python原始字符串(raw strings)的用法

'\b'在ASCII字符中和正则表达式中有不同的含义,我们不得不在字符串中使用'\\b',所以使用正则表达式时我们通常都使用原始字符串。

15.4正则表达式示例

 1 from random import randint, choice
 2 from string import lowercase
 3 from sys import maxint
 4 from time import ctime
 5 
 6 doms = ('com', 'edu', 'net', 'org', 'gov')
 7 
 8 for i in range(randint(5,10)):
 9     dtint = randint(0, maxint - 1)
10     dtstr = ctime(dtint)
11     
12     shorter = randint(4, 7)
13     em = ''
14     for j in range(shorter):
15         em += choice(lowercase)
16 
17     longer = randint(shorter, 12)
18     dn = ''
19     for j in range(longer):
20         dn += choice(lowercase)
21 
22     print '%s::%s%s.%s::%d-%d-%d' %(dtstr, em, dn, choice(doms),
23     dtint, shorter, longer)
gendata.py

15.4.1匹配一个字符串

^(Mon|Tue|Wed|Thu|Fri|Sat|Sun)来匹配日期开头,也可以使用'^(\w{3})',书中提到的'^(\w){3}'在2.7.9环境下并无差异。

15.4.2 搜索与匹配的比较, “贪婪”匹配

在我们写正则表达式前,先明确这些整数数据项是在字符串数据的末尾。这意味着我们有两种选择:搜索(search)或匹配(match)。使用搜索更合适,因为我们确切地知道要查找的数据是什么(三个整数的集合),它不在字符串的开头,也不是字符串的全部内容。如果我们用匹配(match)的方法,就不得不写一个正则表达式来匹配整行内容,并用子组保存我们感兴趣的那部分数据。为说明它们之间的区别,我们先用搜索查找,再尝试用匹配来做,向你证明搜索查找更适合。

>>> patt = '\d+-\d+-\d+'
>>> re.search(patt, data).group()  # entire match  
'1171590364-6-8'

尝试用这个正则表达式来匹配数据会失败,这是为什么呢?因为匹配从字符串的起始位置开始进行的,而我们要找的数值字符串在末尾。我们只能再写一个匹配全部字符串的正则表达式。还有一个偷懒的办法,就是用“.+”来表示任意个字符集,后面再接上我们真正感兴趣的数据:


patt = '.+\d+-\d+-\d+'
>>> re.match(patt, data).group() # entire match  #全部匹配部分
'Thu Feb 15 17:46:04 2007::uzifzf@dpyivihw.gov::1171590364-6-8'


这个方法不错,可是我们只想获得每行末尾数字的字段,而不是整个字符串,所以需要用圆括号将我们感兴趣的那部分数据分成一组:


>>> patt = '.+(\d+-\d+-\d+)'
>>> re.match(patt, data).group(1)  # subgroup 1  #子组 1
'4-6-8'

到底怎么回事呢? 我们本应该得到数据“1171590364-6-8”,而不应该是“4-6-8”啊。第一个整数字段的前半部分到哪里去了呢? 原因是:正则表达式本身默认是贪心匹配的。也就是说,如果正则表达式模式中使用到通配字,那它在按照从左到右的顺序求值时,会尽量“抓取”满足匹配的最长字符串。在我们上面的例子里,“.+”会从字符串的起始处抓取满足模式的最长字符,其中包括我们想得到的第一个整数字段的中的大部分。“\d+”只需一位数字就可以匹配,所以它匹配了数字“4”,而“.+” 则匹配了从字符串起始到这个第一位数字“4”之间的所有字符:

“Thu Feb 15 17:46:04 2007::uzifzf@dpyivihw.gov::117159036”。
一个解决办法是用“非贪婪”操作符,“?”. 这个操作符可以用在 “*”, “+”, 或 “?” 的后面。它的作用是要求正则表达式引擎匹配的字符越少越好。因此,如果我们把“?”放在“.+”的后面,我们就得到了想要的结果.
>>> patt = '.+?(\d+-\d+-\d+)'
>>> re.match(patt, data).group(1)  # subgroup 1   # 子组 1
'1171590364-6-8'

 另一种办法,更简单,注意运用 “::”做字段分隔符号。你可以用一般字符串的 strip('::') 方法,得到全部字符,然后用 strip('-')得到你要找的三个整数字段。我们现在不采用这种方法,因为我们的脚本 gendata.py 正是通过这种方法把字符组合到一起的。

最后一个例子:假设我们只想抽取三个整数字段里中间的那个整数部分。我们是这么做的(用搜索,这样就不必匹配整个字符了):“-(\d+)-”。用这个模式“-(\d+)-”,我们得到:
>>> patt = '-(\d+)-'
>>> m = re.search(patt, data)
>>> m.group()  # entire match   #整个匹配
'-6-'
>>> m.group(1)  # subgroup 1   #子组 1
'6'
在本章中,有很多正则表达式的强大功能我们未能涉及,由于篇幅所限,我们无法详细介绍它们。但我们希望所提供的信息和技巧对你的编程实践有所帮助。我们建议你参阅有关文档以获得更多在 Python语言中使用正则表达式的知识。要精通正则表达式,我们建议你阅读Jeffrey E. F. Friedl 所编写的 Mastering Regular Expressions 一书.

 

 

                

posted @ 2015-06-19 23:24  autoria  阅读(297)  评论(0)    收藏  举报