正则表达式

前言

在python里查找,替换字符串的方法是:index()、 find()、split()、 count()、 replace()等。但这些方法都只是最简单的字符串处理。比如:用index()方法查找单个子字符串,而且查找总是区分大小写的。为了使用不区分大小写的查找,可以使用s.lower()或者s.upper(),但要确认你查找的字符串的大小写是匹配的。replace() 和split() 方法有相同的限制。

如果使用string的方法就可以达到你的目的,那么你就使用它们。它们速度快又简单,并且很容易阅读。但是如果你发现自己要使用大量的if语句,以及很多字符串函数来处理一些特例,或者说你需要组合调用split() 和 join() 来切片、合并你的字符串,你就应该使用正则表达式。

阅读re模块的摘要信息可以了解到一些处理函数以及它们参数的一些概况。

>>> 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’仅仅出现在字符串结尾的正则表达式。$ 表示“字符串结尾”。(还有一个相应的表示“字符串开头”的字符 ^ )。正则表达式模块的re.sub()函数可以做字符串替换,它在字符串s中用正则表达式‘ROAD$’来搜索并替换成‘RD.’。它只会匹配字符串结尾的‘ROAD’,而不会匹配到‘BROAD’中的‘ROAD’,因为这种情况它在字符串的中间。

为了在正则表达式中表达这个独立的词,你可以使用‘\b’。它的意思是“在右边必须有一个分隔符”。

>>> re.sub('\\bROAD$', 'RD.', s)   ①
'100 BROAD'

为了解决‘\’字符传染的问题,可以使用原始字符串。这只需要在字符串的前面添加一个字符‘r’。它告诉python,字符串中没有任何字符需要转义。‘\t’是一个制表符,但r‘\t’只是一个字符‘\’紧跟着一个字符t。我建议在处理正则表达式的时候总是使用原始字符串。否则,会因为理解正则表达式而消耗大量时间(本身正则表达式就已经够让人困惑的了)。

>>> re.sub(r'\bROAD$', 'RD.', s)   ②
'100 BROAD'

案例研究: 罗马数字

你肯定见过罗马数字,即使你不认识他们。你可能在版权信息、老电影、电视、大学或者图书馆的题词墙看到(用Copyright MCMXLVI” 表示版权信息,而不是用 “Copyright 1946”),你也可能在大纲或者目录参考中看到他们。这种系统的数字表达方式可以追溯到罗马帝国(因此而得名)。

在罗马数字中,有七个不同的数字可以以不同的方式结合起来表示其他数字。

  I = 1
    V = 5
    X = 10
    L = 50
    C = 100
    D = 500
    M = 1000

下面是几个通常的规则来构成罗马数字:

  1. 大部分时候用字符相叠加来表示数字。I是1, II是2, III是3。VI是6(挨个看来,是“5 和 1”的组合),VII是7,VIII是8。
  2. 含有10的字符(I,X,C和M)最多可以重复出现三个。为了表示4,必须用同一位数的下一个更大的数字5来减去一。不能用IIII来表示4,而应该是IV(意思是比5小1)。40写做XL(比50小10),41写做XLI,42写做XLII,43写做XLIII,44写做XLIV(比50小10并且比5小1)。
  3. 有些时候表示方法恰恰相反。为了表示一个中间的数字,需要从一个最终的值来减。比如:9需要从10来减:8是VIII,但9确是IX(比10小1),并不是VIII(I字符不能重复4次)。90是XC,900是CM。
  4. 表示5的字符不能在一个数字中重复出现。10只能用X表示,不能用VV表示。100只能用C表示,而不是LL。
  5. 罗马数字是从左到右来计算,因此字符的顺序非常重要。DC表示600,而CD完全是另一个数字400(比500小100)。CI是101,IC不是一个罗马数字(因为你不能从100减1,你只能写成XCIX,表示比100小10,且比10小1)。

re模块最基本的方法是search()函数。

>>> import re
>>> pattern = '^M?M?M?$'        
>>> re.search(pattern, 'M')     
<_sre.SRE_Match object at 0106FB58>
>>> re.search(pattern, 'MM')  
>>> re.search(pattern, '')      ⑥
<_sre.SRE_Match object at 0106F4A8> 

有趣的是,空字符串也能匹配成功,因为正则表达式中的所有M都是可选的。

? 表示匹配是可选的

使用语法{n,m},{1,4} 匹配1到4个前面的模式

>>> pattern = '^M{0,3}$'  

这个正则表达式的意思是“匹配字符串开始,然后是任意的0到3个M字符,再是字符串结尾”。0和3的位置可以写任意的数字。如果你想表示可以匹配的最小次数为1次,最多为3次M字符,可以写成M{1,3}。

(A|B) 匹配A模式或者B模式中的一个
记住:(A|B|C)的意思是“只匹配A,B或者C中的一个”。你匹配了XL,因此XC和L?X?X?X?被忽略,紧接着将检查字符串结尾。MCMXL在罗马数字中表示1940。

>>> pattern = '^M?M?M?(CM|CD|D?C?C?C?)(XC|XL|L?X?X?X?)$'
>>> 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, 'M', re.VERBOSE)                 ①
<_sre.SRE_Match object at 0x008EEB48>

注意,如果要使用松散正则表达式,需要传递一个叫re.VERBOSE的参数。就像你看到的那样,正则表达式中有很多空白符,他们都被忽略掉了。还有一些注释信息,当然也被正则表达式忽略掉。当空白符和注释信息被忽略掉后,这个正则表达式和上面的是完全一样的,但是它有更高的可读性。

案例研究: 解析电话号码

\d 匹配所有0-9的数字. \D 匹配除了数字外的所有字符

>>> 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')                 ③
>>> phonePattern.search('800-555-1212-1234').groups()        ④
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'groups'
  • 我们通常从左到右的阅读正则表达式。首先是匹配字符串开始位置,然后是(\d{3})。\d{3}表示什么意思?\d表示任意的数字(0到9),{3}表示一定要匹配3个数字。这个是你前面看到的{n,m}表示方法。把他们放在圆括号中,表示必须匹配3个数字,并且把他们记做一个组。分组的概念我们后面会说到。
  • 为了使用正则表达式匹配到的这些分组,需要对search()函数的返回值调用groups()方法。它会返回一个这个正则表达式中定义的所有分组结果组成的元组。
>>> phonePattern = re.compile(r'^(\d{3})-(\d{3})-(\d{4})-(\d+)$') 

(\d+)这个分组里的内容是匹配一个或更多个数字

>>> phonePattern = re.compile(r'^(\d{3})\D+(\d{3})\D+(\d{4})\D+(\d+)$')

\D+,这是什么?好吧,\D匹配除了数字以外的任意字符,+的意思是一个或多个。因此\D+匹配一个或一个以上的非数字字符。这就是你用来替换连字符的东西,它用来匹配不同的分隔符。
用\D+替换-,意味着你可以匹配分隔符为空格的情况。

用正则表达式处理电话号码没有分隔符的情况。 
>>> phonePattern = re.compile(r'^(\d{3})\D*(\d{3})\D*(\d{4})\D*(\d*)$') 

把所有的+换成了。号码之间的分隔符不再用\D+来匹配,而是使用\D还记得+表示一个或更多吧?好,现在可以解析号码之间没有分隔符的情况了。

现在在字符串的开头可能有一些你想忽略掉的不确定的字符。为了匹配到想要的数据,你需要跳过他们。我们来看看不明确匹配字符串开始的方法。 
>>> phonePattern = re.compile(r'(\d{3})\D*(\d{3})\D*(\d{4})\D*(\d*)$') 

这是最终的答案!注意正则表达式没有^。不会再匹配字符串开始位置了。正则表达式不会匹配整个字符串,而是试图找到一个字符串开始匹配的位置,然后从这个位置开始匹配。

第二种松散正则表达式:

>>> 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')

小结

这只是正则表达式能完成的工作中的冰山一角。换句话说,尽管你可能很受打击,相信我,你已经不是什么都不知道了。

现在,你应该已经熟悉了下面的技巧:

^ 匹配字符串开始位置。
$ 匹配字符串结束位置。
\b 匹配一个单词边界。
\d 匹配一个数字。
\D 匹配一个任意的非数字字符。
x? 匹配可选的x字符。换句话说,就是0个或者1个x字符。
x* 匹配0个或更多的x。
x+ 匹配1个或者更多x。
x{n,m} 匹配n到m个x,至少n个,不能超过m个。
(a|b|c) 匹配单独的任意一个a或者b或者c。
(x) 这是一个组,它会记忆它匹配到的字符串。你可以用re.search返回的匹配对象的groups()函数来获取到匹配的值。 

正则表达式非常强大,但它也并不是解决每一个问题的正确答案。你需要更多的了解来判断哪些情况适合使用正则表达式。某些时候它可以解决你的问题,某些时候它可能带来更多的问题。

参考:
http://old.sebug.net/paper/books/dive-into-python3/regular-expressions.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值