Python 核心编程(第二版)——正则表达式
1. 介绍
正则表达式(RE)为高级文本模式匹配,以及搜索-替代等功能提供了基础。正则表达式(RE)是一些由字符和特殊符号组成的字符串,它们描述了这些字符和字符的某种重复方式,因此能按某种模式匹配一个有相似特征的字符串的集合,因此能按某模式匹配一系列有相似特征的字符串。
核心笔记:查找与匹配的比较
与字符串中模式有关的正则表达式时,我们会用术语 “matching”(“匹配”),指的是术语pattern-matching(模式匹配)。在Python专门术语中,有两种主要方法完成模式匹配:搜索(searching)和匹配(matching)。搜索,即在字符串任意部分中查找匹配的模式,而匹配是指,判断一个字符串能否从起始处全部或部分的匹配某个模式。搜索通过search()函数或方法来实现,而匹配是以调用match()函数或方法实现的。总之,当我们说模式的时候,我们全部使用术语“matching”(“匹配”);我们按照Python 如何完成模式匹配的方式来区分“搜索”和“匹配”。“matching”是试图从整个字符串的开头进行匹配,而 “searching” 则可从一个字符串的任意位置开始匹配。
正则表达式是含有文本和特别字符的字符串,这些文本和特别字符描述的模式可以识别各种字符串。以下的正则表达式是最基本,最普通的。它们仅由一个字符串定义了一个模式,该模式仅匹配这个字符串本身,该字符串由正则表达式定义。以下是正则表达式(RE)和匹配它们的字符串。
RE Pattern | String(s) Matched |
foo | foo |
Python | Python |
abc123 | abc123 |
正则表达式的强大之处在于特殊符号的应用,特殊符号定义了字符集合,子组匹配,模式重复次数。正是这些特殊符号使得一个正则表达式可以匹配字符串集合而不只是一个字符串。
2. 正则表达式使用的特殊符号和字符
最常用的元字符(metacharacters)——特殊字符和符号,正是它们赋予了正则表达式强大的功能和灵活性。正则表达式中最常见的符号和字符见下表:
常用正则表达式符号和特殊字符 | ||
记号 Symbols | 说明 | 举例 |
literal | 匹配字符串的值 | foo |
rel1 | rel2 | 匹配正则表达式rel1 或 rel2 | 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] |
(...) | 匹配封闭括号中正则表达式(RE),并保存为子组 |
([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 |
管道符号( | ), 就是您键盘上的竖杠,表示一个或操作,它的意思是选择被管道符号分隔的多个不同的正则表达式中的一个。有了这个符号,正则表达式的灵活性增强了,使得它可以匹配不止一个字符串,“或”(操作)有时候也被叫做“联合”(union)或者逻辑或(OR)。
点字符或句号(.)符号匹配除换行符(NEWLINE)外的任意一个单个字符(Python 的正则表达式有一个编译标识 [S or DOTALL],该标识能去掉这一限制,使 ( . ) 在匹配时包括换行符(NEWLINEs)。)。如何才能匹配点号(dot)或句号(period)?为了明确地匹配一个点号(dot)本身,你必须(在前面)使用反斜线“\”对它进行转义。
如果想从字符串的开头开始匹配一个模式,你必须用脱字符号( ^ , 即,Caret)或特殊字符 \A (大写字母A 前面加上一个反斜线). 后者主要是为那些没有caret 符号的键盘使用的,比如说国际键盘。类似,美元符号 ( $ ) 或 \Z 是用来(零宽度)匹配字符串的结尾的。
特别说明,如果你想匹配这两个字符中的任何一个(或全部),就必须用反斜线进行转义。
特殊字符 \b and \B 用来匹配单词边界。两者之间的区别是,\b 匹配的模式是一个单词边界,就是说,与之对应的模式一定在一个单词的开头,不论这个单词的前面是有字符(该词在一个字符串的中间),还是没有字符(该单词在一行的起始处)。同样地,\B 只匹配出现在一个单词中间的模式(即,不在单词边界上的字符)。
尽管点号可用来匹配任意字符,但有时候你需要匹配某些个特殊的字符。正因为如此,方括号( [ ] )被发明出来。使用方括号的正则表达式会匹配方括号里的任何一个字符。方括号[]只适用于单个字符的情况。
方括号除匹配单个字符外,还可以支持所指定的字符范围。方括号里一对符号中间的连字符(-)用来表示一个字符的范围([..a-z..]),例如,A–Z, a–z, 或 0–9 分别代表大写字母、小写字母和十进制数字。这是一个按字母顺序排序的范围,所以它不限于只用在字母和十进制数字上。另外,如果在左方括号后第一个字符是上箭头符号(^)([^...]),就表示不匹配指定字符集里的任意字符。
最常用的正则表达式符号,即特殊符号 “*”, “+”, 和 “?”, 它们可以用于匹配字符串模式出现一次、多次、或未出现的情况。星号或称星号操作符匹配它左边那个正则表达式出现零次或零次以上的情况;加号(+)操作符匹配它左边那个正则表达式模式至少出现一次的情况;而问号操作符( ? )匹配它左边那个正则表达式模式出现零次或一次的情况。花括号操作符({ }), 花括号里可以是单个的值,也可以是由逗号分开的一对值。如果是一个值,如,{N},则表示匹配N 次出现;如果是一对值,即,{M, N},就表示匹配M 次到N 次出现。可以在这些符号前用反斜线进行转义,使它们失去特殊作用,即, “\*” 将匹配星号本身等。
可以不使用 “0–9”这个范围表示十进制数字,而改用简写“\d”表示。另一个特殊的字符 “\w” 可用来表示整个字符数字的字符集,即相当于“A-Za-z0-9_”的简写形式,特殊字符“\s” 代表空白字符。
我们不仅想知道是否整个字符串匹配我们的条件(正则表达式),还想在匹配成功时取出某个特定的字符串或子字符串。要达到这个目的,只需要给正则表达式的两边加上一对圆括号。一对圆括号(()) 和正则表达式一起使用时可以实现以下任意一个(或两个)功能:
对正则表达式进行分组
匹配子组
使用圆括号的一个额外好处就是匹配的子串会被保存到一个子组,便于今后使用。这些子组可以在同一次匹配或搜索中被重复调用,或被提取出来做进一步处理。为什么需要使用子组匹配呢? 主要是有时除了进行匹配操作外,你还想要提取匹配模式的内容。
3. 正则表达式和Python 语言
常见的正则表达式函数与方法 | |
函数/方法 | 描述 |
re 模块的函数 | |
compile(pattern,flags=0) | 对正则表达式模式pattern 进行编译,flags 是可选标志符,并返回一个regex 对象 |
re 模块的函数和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() | 返回一个包含全部匹配的子组的元组(如果没有成功匹配,就返回一个空元组) |
核心笔记: RE 编译(何时应该使用compile 函数?)
过Python 的代码最终会被编译为字节码,然后才被解释器执行。调用eval() 或 exec()调用一个代码对象而不是一个字符串,在性能上会有明显地提升,这是因为对前者来说, 编译过程不必执行。换句话说,使用预编译代码对象要比使用字符串快,因为解释器在执行字符串形式的代码前必须先把它编译成代码对象。
在模式匹配之前,正则表达式模式必须先被编译成regex 对象。由于正则表达式在执行过程中被多次用于比较,我们强烈建议先对它做预编译,而且,既然正则表达式的编译是必须的,那使用么预先编译来提升执行性能无疑是明智之举。re.compile() 就是用来提供此功能的。
注意:尽管我们建议预编译,但它并不是必需的。如果你需要编译,就用方法,如果不需要,可以使用函数。
如果你想在regex 对象的方法中使用这些标志符,则必须在编译对象时传递这些参数。regex 对象还有一些数据属性,其中两个是创建时给定的编译标志符和正则表达式模式。
在处理正则表达式时,除regex 对象外,还有另一种对象类型 - 匹配对象。这些对象是在match()或search()被成功调用之后所返回的结果。匹配对象有两个主要方法:group() 和 groups()。group()方法或者返回所有匹配对象或是根据要求返回某个特定子组。groups()则很简单,它返回一个包含唯一或所有子组的元组。如果正则表达式中没有子组的话, groups() 将返回一个空元组,而group()仍会返回全部匹配对象。
match()函数尝试从字符串的开头开始对模式进行匹配。如果匹配成功,就返回一个匹配对象,而如果匹配失败了,就返回None。匹配对象的group() 方法可以用来显示那个成功的匹配。即使字符串比模式要长,匹配也可能成功;只要模式是从字符串的开始进行匹配的。
search 和match 的工作方式一样,不同之处在于search 会检查参数字符串任意位置的地方给定正则表达式模式的匹配情况。如果搜索到成功的匹配,会返回一个匹配对象,否则返回None。search() 查找字符串中模式首次出现的位置,而不是尝试(在起始处)匹配。严格地说,search() 是从左到右进行搜索。
匹配多个字符串(|):在正则表达式中使用了管道符号。
匹配任意单个字符( . ):点号是不能匹配换行符或非字符(即,空字符串)的。在正则表达式中,用反斜线对点号进行转义,使点号失去它的特殊意义。
正则表达式中最常见的情况包括特殊字符的使用,正则表达式模式的重复出现,以及使用圆括号对匹配模式的各部分进行分组和提取操作。group()通常用来显示所有匹配部分,也可用来获取个别匹配的子组。我们可用groups()方法获得一个包含所有匹配子组的元组。
findall()用于非重叠地查找某字符串中一个正则表达式模式出现的情况。findall()和search()相似之处在于二者都执行字符串搜索,但findall()和match()与search()不同之处是,findall()总返回一个列表。如果findall()没有找到匹配的部分,会返回空列表;如果成功找到匹配部分,则返回所有匹配部分的列表(按从左到右出现的顺序排列)。正则表达式仅有一个子组时,findall()返回子组匹配的字符串组成的列表;如果表达式有多个子组,返回的结果是一个元组的列表,元组中每个元素都是一个子组的匹配内容,像这样的元组(每一个成功的匹配对应一个元组)构成了返回列表中的元素。
有两种函数/方法用于完成搜索和代替的功能: sub()和subn()。 二者几乎是一样的,都是将某字符串中所有匹配正则表达式模式的部分进行替换。用来替换的部分通常是一个字符串,但也可能是一个函数,该函数返回一个用来替换的字符串。subn()和sub()一样,但它还返回一个表示替换次数的数字,替换后的字符串和表示替换次数的数字作为一个元组的元素返回。
re.sub(pattern,string,expression)
返回一个用来替换的字符串。
re.subn(pattern,string,expression)
返回一个替换后的字符串和表示替换次数的数字作为一个元组的元素返回。
用split()分割(分隔模式):re 模块和正则表达式对象的方法split()与字符串的split()方法相似,前者是根据正则表达式模式分隔字符串,后者是根据固定的字符串分割,因此与后者相比,显著提升了字符分割的能力。如果分隔符没有使用由特殊符号表示的正则表达式来匹配多个模式,那re.split()和string.split()的执行过程是一样的。但运用正则表达式后,我们会发现re.split()成了一个功能更强大的工具。
要从我们写的脚本里调用另一个程序,可以用os.popen()命令。
需要注意的是正则表达式的特殊字符和特殊ASCII 字符是容易混淆的。我们可能用\n 来表示一个ASCII 换行字符,但也可以用\d 表示匹配一个数字的正则表达式。如果同一个符号在ASCII 和正则表达式中都可以用,就容易出问题了。所以推荐使用Python 语言中的"原始字符串"来避免混淆。
核心笔记 : Python 原始字符串(raw strings)的用法
原始字符串的产生正是由于有正则表达式的存在。原因是ASCII 字符和正则表达式特殊字符间所产生的冲突。
1 >>> m = re.match('\bblow', 'blow') # backspace, no match #退格键,没有匹配 2 >>> if m is not None: m.group() 3 ... 4 >>> m = re.match('\\bblow', 'blow') # escaped \, now it works #用\转义后,现在匹配了 5 >>> if m is not None: m.group() 6 ... 7 'blow' 8 >>> m = re.match(r'\bblow', 'blow') # use raw string instead #改用原始字符串 9 >>> if m is not None: m.group() 10 ... 11 'blow'
4. 正则表达式示例
1 >>> import re 2 >>> data = 'Thu Feb 15 17:46:04 2007::uzifzf@dpyivihw.gov::1171590364-6-8' 3 >>> patt = '^(Mon|Tue|Wed|Thu|Fri|Sat|Sun)' 4 >>> m = re.match(patt, data) 5 >>> m.group() # entire match 6 'Thu' 7 8 >>> patt = '\d+-\d+-\d+' 9 >>> re.search(patt, data).group() # entire match #全部匹配部分 10 '1171590364-6-8' 11 12 >>> patt = '.+(\d+-\d+-\d+)' 13 >>> re.match(patt, data).group(1) # subgroup 1 #子组1 14 '4-6-8' 15 16 >>> patt = '.+?(\d+-\d+-\d+)' 17 >>> re.match(patt, data).group(1) # subgroup 1 # 子组1 18 '1171590364-6-8'
一个解决办法是用“非贪婪”操作符,“?”. 这个操作符可以用在 “*”, “+”, 或 “?” 的后面。它的作用是要求正则表达式引擎匹配的字符越少越好。另一种办法,更简单,注意运用 “::”做字段分隔符号。你可以用一般字符串的strip('::') 方法,得到全部字符,然后用strip('-')得到你要找的三个整数字段。