标签:
正则表达式是搜索、替换和解析复杂字符模式的一种强大而标准的方法。如果你曾经在其他语言 (如 Perl) 中使用过它,由于它们的语法非常相似,你仅仅阅读一下 re 模块的摘要,大致了解其中可用的函数和参数就可以了。
字符串也有很多方法,可以进行搜索 (index、find 和 count)、替换 (replace) 和解析 (split),但它们仅限于处理最简单的情况。搜索方法查找单个和固定编码的子串,并且它们总是大小写敏感的。对一个字符串s,如果要进行大小写不敏感的搜索,则你必须调用 s.lower() 或 s.upper() 将 s 转换成全小写或者全大写,然后确保搜索串有着相匹配的大小写。replace 和 split方法有着类似的限制。
如果你要解决的问题利用字符串函数能够完成,你应该使用它们。它们快速、简单且容易阅读,而快速、简单、可读性强的代码可以说出很多好处。但是,如果你发现你使用了许多不同的字符串函数和 if 语句来处理一个特殊情况,或者你组合使用了 split、join 等函数而导致用一种奇怪的甚至读不下去的方式理解列表,此时,你也许需要转到正则表达式了。
尽管正则表达式语法较之普通代码相对麻烦一些,但是却可以得到更可读的结果,与用一长串字符串函数的解决方案相比要好很多。在正则表达式内部有多种方法嵌入注释,从而使之具有自文档化 (self-documenting) 的能力。
这一系列的例子是由我几年前日常工作中的现实问题启发而来的,当时我需要从一个老化系统中导出街道地址,在将它们导入新的系统之前,进行清理和标准化。(看,我不是只将这些东西堆到一起,它有实际的用处。)这个例子展示我如何处理这个问题。
>>> s = ‘100 NORTH MAIN ROAD‘
>>> s.replace(‘ROAD‘, ‘RD.‘)
‘100 NORTH MAIN RD.‘
>>> s = ‘100 NORTH BROAD ROAD‘
>>> s.replace(‘ROAD‘, ‘RD.‘)
‘100 NORTH BRD. RD.‘
>>> s[:-4] + s[-4:].replace(‘ROAD‘, ‘RD.‘)
‘100 NORTH BROAD RD.‘
>>> import re
>>> re.sub(‘ROAD$‘, ‘RD.‘, s)
‘100 NORTH BROAD RD.‘
继续我的清理地址的故事。很快我发现,在上面的例子中,仅仅匹配地址末尾的 ‘ROAD‘ 不是很好,因为不是所有的地址都包括表示街道的单词 (‘ROAD‘);有一些直接以街道名结尾。大部分情况下,不会遇到这种情况,但是,如果街道名称为 ‘BROAD‘,那么正则表达式将会匹配 ‘BROAD‘ 的一部分为 ‘ROAD‘,而这并不是我想要的。
>>> s = ‘100 BROAD‘
>>> re.sub(‘ROAD$‘, ‘RD.‘, s)
‘100 BRD.‘
>>> re.sub(‘\\bROAD$‘, ‘RD.‘, s)
‘100 BROAD‘
>>> re.sub(r‘\bROAD$‘, ‘RD.‘, s)
‘100 BROAD‘
>>> s = ‘100 BROAD ROAD APT. 3‘
>>> re.sub(r‘\bROAD$‘, ‘RD.‘, s)
‘100 BROAD ROAD APT. 3‘
>>> re.sub(r‘\bROAD\b‘, ‘RD.‘, s)
‘100 BROAD RD. APT 3‘
你可能经常看到罗马数字,即使你没有意识到它们。你可能曾经在老电影或者电视中看到它们 (“版权所有 MCMXLVI” 而不是 “版权所有1946”),或者在某图书馆或某大学的贡献墙上看到它们 (“成立于 MDCCCLXXXVIII”而不是“成立于1888”)。你也可能在某些文献的大纲或者目录上看到它们。这是一个表示数字的系统,它实际上能够追溯到远古的罗马帝国 (因此而得名)。
在罗马数字中,利用7个不同字母进行重复或者组合来表达各式各样的数字。
下面是关于构造罗马数字的一些通用的规则的介绍:
怎样校验任意一个字符串是否为一个有效的罗马数字呢?我们每次只看一位数字,由于罗马数字一般是从高位到低位书写。我们从高位开始:千位。对于大于或等于 1000 的数字,千位由一系列的字符 M 表示。
>>> import re
>>> pattern = ‘^M?M?M?$‘
>>> re.search(pattern, ‘M‘)
<SRE_Match object at 0106FB58>
>>> re.search(pattern, ‘MM‘)
<SRE_Match object at 0106C290>
>>> re.search(pattern, ‘MMM‘)
<SRE_Match object at 0106AA38>
>>> re.search(pattern, ‘MMMM‘)
>>> re.search(pattern, ‘‘)
<SRE_Match object at 0106F4A8>
与千位数相比,百位数识别起来要困难得多,这是因为有多种相互独立的表达方式都可以表达百位数,而具体用那种方式表达和具体的数值有关。
因此有四种可能的模式:
后面两个模式可以结合到一起:
这个例子显示如何有效地识别罗马数字的百位数。
>>> import re
>>> pattern = ‘^M?M?M?(CM|CD|D?C?C?C?)$‘
>>> re.search(pattern, ‘MCM‘)
<SRE_Match object at 01070390>
>>> re.search(pattern, ‘MD‘)
<SRE_Match object at 01073A50>
>>> re.search(pattern, ‘MMMCCC‘)
<SRE_Match object at 010748A8>
>>> re.search(pattern, ‘MCMC‘)
>>> re.search(pattern, ‘‘)
<SRE_Match object at 01071D98>
哎呀!看看正则表达式能够多快变得难以理解?你仅仅表示了罗马数字的千位和百位上的数字。如果你根据类似的方法,十位数和各位数就非常简单了,因为是完全相同的模式。让我们来看表达这个模式的另一种方式吧。
在前面的章节,你处理了相同字符可以重复三次的情况。在正则表达式中,有另外一个方式来表达这种情况,并且能提高代码的可读性。首先看看我们在前面的例子中使用的方法。
>>> import re
>>> pattern = ‘^M?M?M?$‘
>>> re.search(pattern, ‘M‘)
<_sre.SRE_Match object at 0x008EE090>
>>> pattern = ‘^M?M?M?$‘
>>> re.search(pattern, ‘MM‘)
<_sre.SRE_Match object at 0x008EEB48>
>>> pattern = ‘^M?M?M?$‘
>>> re.search(pattern, ‘MMM‘)
<_sre.SRE_Match object at 0x008EE090>
>>> re.search(pattern, ‘MMMM‘)
>>>
>>> pattern = ‘^M{0,3}$‘
>>> re.search(pattern, ‘M‘)
<_sre.SRE_Match object at 0x008EEB48>
>>> re.search(pattern, ‘MM‘)
<_sre.SRE_Match object at 0x008EE090>
>>> re.search(pattern, ‘MMM‘)
<_sre.SRE_Match object at 0x008EEDA8>
>>> re.search(pattern, ‘MMMM‘)
>>>
没有一个轻松的方法来确定两个正则表达式是否等价。你能采用的最好的办法就是列出很多的测试样例,确定这两个正则表达式对所有的相关输入都有相同的输出。在本书后面的章节,将更多地讨论如何编写测试样例。 |
现在我们来扩展一下关于罗马数字的正则表达式,以匹配十位数和个位数,下面的例子展示十位数的校验方法。
>>> pattern = ‘^M?M?M?(CM|CD|D?C?C?C?)(XC|XL|L?X?X?X?)$‘
>>> re.search(pattern, ‘MCMXL‘)
<_sre.SRE_Match object at 0x008EEB48>
>>> re.search(pattern, ‘MCML‘)
<_sre.SRE_Match object at 0x008EEB48>
>>> re.search(pattern, ‘MCMLX‘)
<_sre.SRE_Match object at 0x008EEB48>
>>> re.search(pattern, ‘MCMLXXX‘)
<_sre.SRE_Match object at 0x008EEB48>
>>> re.search(pattern, ‘MCMLXXXX‘)
>>>
对于个位数的正则表达式有类似的表达方式,我将省略细节,直接展示结果。
>>> pattern = ‘^M?M?M?(CM|CD|D?C?C?C?)(XC|XL|L?X?X?X?)(IX|IV|V?I?I?I?)$‘
用另一种 {n,m} 语法表达这个正则表达式会如何呢?这个例子展示新的语法。
>>> pattern = ‘^M{0,3}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$‘
>>> re.search(pattern, ‘MDLV‘)
<_sre.SRE_Match object at 0x008EEB48>
>>> re.search(pattern, ‘MMDCLXVI‘)
<_sre.SRE_Match object at 0x008EEB48>
>>> re.search(pattern, ‘MMMDCCCLXXXVIII‘)
<_sre.SRE_Match object at 0x008EEB48>
>>> re.search(pattern, ‘I‘)
<_sre.SRE_Match object at 0x008EEB48>
如果你在第一遍就跟上并理解了所讲的这些,那么你做的比我还要好。现在,你可以尝试着理解别人大规模程序里关键函数中的正则表达式了。或者想象着几个月后回头理解你自己的正则表达式。我曾经做过这样的事情,但是它并不是那么有趣。
在下一节里,你将会研究另外一种正则表达式语法,它可以使你的表达式具有更好的可维持性。
迄今为止,你只是处理过被我称之为“紧凑”类型的正则表达式。正如你曾看到的,它们难以阅读,即使你清楚正则表达式的含义,你也不能保证六个月以后你还能理解它。你真正所需的就是利用内联文档 (inline documentation)。
Python 允许用户利用所谓的松散正则表达式 来完成这个任务。一个松散正则表达式和一个紧凑正则表达式主要区别表现在两个方面:
用一个例子可以解释得更清楚。让我们重新来看前面的紧凑正则表达式,利用松散正则表达式重新表达。下面的例子显示实现方法。
>>> pattern = """
^ # beginning of string
M{0,3} # thousands - 0 to 3 M‘s
(CM|CD|D?C{0,3}) # hundreds - 900 (CM), 400 (CD), 0-300 (0 to 3 C‘s),
# or 500-800 (D, followed by 0 to 3 C‘s)
(XC|XL|L?X{0,3}) # tens - 90 (XC), 40 (XL), 0-30 (0 to 3 X‘s),
# or 50-80 (L, followed by 0 to 3 X‘s)
(IX|IV|V?I{0,3}) # ones - 9 (IX), 4 (IV), 0-3 (0 to 3 I‘s),
# or 5-8 (V, followed by 0 to 3 I‘s)
$ # end of string
"""
>>> re.search(pattern, ‘M‘, re.VERBOSE)
<_sre.SRE_Match object at 0x008EEB48>
>>> re.search(pattern, ‘MCMLXXXIX‘, re.VERBOSE)
<_sre.SRE_Match object at 0x008EEB48>
>>> re.search(pattern, ‘MMMDCCCLXXXVIII‘, re.VERBOSE)
<_sre.SRE_Match object at 0x008EEB48>
>>> re.search(pattern, ‘M‘)
当使用松散正则表达式时,最重要的一件事情就是:必须传递一个额外的参数 re.VERBOSE,该参数是定义在 re 模块中的一个常量,标志着待匹配的正则表达式是一个松散正则表达式。正如你看到的,这个模式中,有很多空格 (所有的空格都被忽略),和几个注释 (所有的注释也被忽略)。如果忽略所有的空格和注释,它就和前面章节里的正则表达式完全相同,但是具有更好的可读性。 | |
这个模式匹配字符串的开始,接着匹配三个可选 M 字符中的一个,接着匹配 CM,接着是字符 L 和三个可选 X 字符的所有字符,接着是 IX,然后是字符串的结尾。 | |
这个模式匹配字符串的开始,接着是三个可选的 M 字符的所有字符,接着匹配 D?C{0,3},此处为一个字符 D 和三个可选 C 字符中所有字符,接着匹配 L?X{0,3},此处为一个 L 字符和三个可选 X 字符中所有字符,接着匹配 V?I{0,3},此处为一个字符 V 和三个可选 I 字符中所有字符,接着匹配字符串的结尾。 | |
这个没有匹配。为什么呢?因为没有 re.VERBOSE 标记,所以 re.search 函数把模式作为一个紧凑正则表达式进行匹配。Python 不能自动检测一个正则表达式是为松散类型还是紧凑类型。Python 默认每一个正则表达式都是紧凑类型的,除非你显式地标明一个正则表达式为松散类型。 |
迄今为止,你主要是匹配整个模式,不论是匹配上,还是没有匹配上。但是正则表达式还有比这更为强大的功能。当一个模式确实 匹配上时,你可以获取模式中特定的片断,你可以发现具体匹配的位置。
这个例子来源于我遇到的另一个现实世界的问题,也是在以前的工作中遇到的。问题是:解析一个美国电话号码。客户要能 (在一个单一的区域中) 输入任何数字,然后存储区号、干线号、电话号和一个可选的独立的分机号到公司数据库里。为此,我通过网络找了很多正则表达式的例子,但是没有一个能够完全 满足我的要求。
这里列举了我必须能够接受的电话号码:
格式可真够多的!我需要知道区号是 800,干线号是 555,电话号的其他数字为 1212。对于那些有分机号的,我需要知道分机号为 1234。
让我们完成电话号码解析这个工作,这个例子展示第一步。
>>> phonePattern = re.compile(r‘^(\d{3})-(\d{3})-(\d{4})$‘)
>>> phonePattern.search(‘800-555-1212‘).groups()
(‘800‘, ‘555‘, ‘1212‘)
>>> phonePattern.search(‘800-555-1212-1234‘)
>>>
我们通常从左到右阅读正则表达式。这个正则表达式匹配字符串的开始,接着匹配 (\d{3})。\d{3} 是什么呢?好吧,{3} 的含义是“精确匹配三个数字”;这是曾在前面见到过的 {n,m} 语法的一种变形。\d 的含义是 “任何一个数字” (0 到 9)。把它们放大括号中意味着要“精确匹配三个数字位,接着把它们作为一个组保存下来,以便后面的调用”。接着匹配一个连字符,接着是另外一个精确匹配三个数字位的组,接着另外一个连字符,接着另外一个精确匹配四个数字为的组,接着匹配字符串的结尾。 | |
为了访问正则表达式解析过程中记忆下来的多个组,我们使用 search 函数返回对象的 groups() 函数。这个函数将返回一个元组,元组中的元素就是正则表达式中定义的组。在这个例子中,定义了三个组,第一个组有三个数字位,第二个组有三个数字位,第三个组有四个数字位。 | |
这个正则表达式不是最终的答案,因为它不能处理在电话号码结尾有分机号的情况,为此,我们需要扩展这个正则表达式。 |
>>> phonePattern = re.compile(r‘^(\d{3})-(\d{3})-(\d{4})-(\d+)$‘)
>>> phonePattern.search(‘800-555-1212-1234‘).groups()
(‘800‘, ‘555‘, ‘1212‘, ‘1234‘)
>>> phonePattern.search(‘800 555 1212 1234‘)
>>>
>>> phonePattern.search(‘800-555-1212‘)
>>>
下一个例子展示正则表达式处理一个电话号码内部,采用不同分隔符的情况。
>>> phonePattern = re.compile(r‘^(\d{3})\D+(\d{3})\D+(\d{4})\D+(\d+)$‘)
>>> phonePattern.search(‘800 555 1212 1234‘).groups()
(‘800‘, ‘555‘, ‘1212‘, ‘1234‘)
>>> phonePattern.search(‘800-555-1212-1234‘).groups()
(‘800‘, ‘555‘, ‘1212‘, ‘1234‘)
>>> phonePattern.search(‘80055512121234‘)
>>>
>>> phonePattern.search(‘800-555-1212‘)
>>>
下一个例子展示正则表达式处理没有 分隔符的电话号码的情况。
>>> phonePattern = re.compile(r‘^(\d{3})\D*(\d{3})\D*(\d{4})\D*(\d*)$‘)
>>> phonePattern.search(‘80055512121234‘).groups()
(‘800‘, ‘555‘, ‘1212‘, ‘1234‘)
>>> phonePattern.search(‘800.555.1212 x1234‘).groups()
(‘800‘, ‘555‘, ‘1212‘, ‘1234‘)
>>> phonePattern.search(‘800-555-1212‘).groups()
(‘800‘, ‘555‘, ‘1212‘, ‘‘)
>>> phonePattern.search(‘(800)5551212 x1234‘)
>>>
下一个例子展示如何解决电话号码前面有其他字符的情况。
>>> phonePattern = re.compile(r‘^\D*(\d{3})\D*(\d{3})\D*(\d{4})\D*(\d*)$‘)
>>> phonePattern.search(‘(800)5551212 ext. 1234‘).groups()
(‘800‘, ‘555‘, ‘1212‘, ‘1234‘)
>>> phonePattern.search(‘800-555-1212‘).groups()
(‘800‘, ‘555‘, ‘1212‘, ‘‘)
>>> phonePattern.search(‘work 1-(800) 555.1212 #1234‘)
>>>
让我们往回看一下。迄今为止,正则表达式总是从一个字符串的开始匹配。但是现在你看到了,有很多不确定的情况需要你忽略。与其尽力全部匹配它们,还不如全部跳过它们,让我们采用一个不同的方法:根本不显式地匹配字符串的开始。下面的这个例子展示这个方法。
>>> phonePattern = re.compile(r‘(\d{3})\D*(\d{3})\D*(\d{4})\D*(\d*)$‘)
>>> phonePattern.search(‘work 1-(800) 555.1212 #1234‘).groups()
(‘800‘, ‘555‘, ‘1212‘, ‘1234‘)
>>> phonePattern.search(‘800-555-1212‘)
(‘800‘, ‘555‘, ‘1212‘, ‘‘)
>>> phonePattern.search(‘80055512121234‘)
(‘800‘, ‘555‘, ‘1212‘, ‘1234‘)
看看一个正则表达式能够失控得多快?回头看看前面的例子,你还能区别它们么?
当你还能够理解这个最终答案的时候 (这个正则表达式就是最终答案,即使你发现一种它不能处理的情况,我也真的不想知道它了),在你忘记为什么你这么选择之前,让我们把它写成松散正则表达式的形式。
>>> phonePattern = re.compile(r‘‘‘
# don‘t match beginning of string, number can start anywhere
(\d{3}) # area code is 3 digits (e.g. ‘800‘)
\D* # optional separator is any number of non-digits
(\d{3}) # trunk is 3 digits (e.g. ‘555‘)
\D* # optional separator
(\d{4}) # rest of number is 4 digits (e.g. ‘1212‘)
\D* # optional separator
(\d*) # extension is optional and can be any number of digits
$ # end of string
‘‘‘, re.VERBOSE)
>>> phonePattern.search(‘work 1-(800) 555.1212 #1234‘).groups()
(‘800‘, ‘555‘, ‘1212‘, ‘1234‘)
>>> phonePattern.search(‘800-555-1212‘)
(‘800‘, ‘555‘, ‘1212‘, ‘‘)
这只是正则表达式能够完成工作的很少一部分。换句话说,即使你现在备受打击,相信我,你也不是什么也没见过了。
现在,你应该熟悉下列技巧:
正则表达式非常强大,但是它并不能为每一个问题提供正确的解决方案。你应该学习足够多的知识,以辨别什么时候它们是合适的,什么时候它们会解决你的问题,什么时候它们产生的问题比要解决的问题还要多。
一些人,遇到一个问题时就想:“我知道,我将使用正则表达式。”现在他有两个问题了。 |
标签:
原文地址:http://www.cnblogs.com/astwish/p/4799878.html