Python中的正则表达式(来自Dive into Python3)

利用python提供的最简单的字符串函数index(), find(), split(), count(), replace()等函数来实现字符串的剪切工作将会十分困难,因为这些函数几乎都是对大小写敏感的。如果你的任务刚好可以用这些函数完成,那就用——毕竟函数简单,代码可读性高。但是如果你的字符串处理程序中不得不出现大量的条件判断或者不断地使用split()和join()进行剪切连接操作,那么你就得考虑正则表达式了。

案例1:街道地址

>>> 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'替换为'RD.'。最初使用字符串特有的replace方法即可完成任务。但是很快遇到了不便,出现了'BROAD ROAD',而replace方法却不能分辨二者的区别,于是改用索引方法实现。但这个方法存在的问题是:整个过程是依赖被搜索字符串长度的,比如若是想搜索'STREET',那就必须搜索最后6个字符——是一种不易扩展的方法。

作者于是改用正则表达式中的re.sub方法,并将正则表达式设置为'ROAD$',其中的$代表匹配字符串末尾。(对应的'^'放在正则表达式之前可以用来匹配字符串首。)

但是作者很快遇到了第二个问题,如果某个地址恰恰是以'BROAD'作为结尾怎么办?’100 BROAD‘会直接被转化成’100 BRD.‘。于是作者进行了进一步的改进:

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

作者在原有的正则表达式之前加入了'\b',旨在作为'ROAD'的边界。这样,就能用以匹配位于末端的独立'ROAD'了。但是在Python字符串中若想输入单独的''就涉及到字符转义的问题——这里不得不对''进行转义,否则解释器会认为’\b‘是一个ascii字符,而不是''+'b'。

所以作者建议书写正则表达式时,一律使用原始字符r'',这样的字符串中每个字符都具有唯一的含义,完全不需要进行字符的转义处理。所以此时的正则表达式为r'\bROAD$'。

很快作者有遇到了更多的问题,有的地址含有独立的'ROAD',但是并不以这个词结尾。于是将正则表达式改为r'\bROAD\b'。

案例2:罗马数字

罗马数字体系中一共有7个字母会被反复、组合使用,它们分别代表:

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

另外,罗马数字的构成还有一些额外的规则:

  • 字符有时是累加的例如,I是1,VI是5+1=6,VIII是5+1+1+1=8;
  • I,X,C,M四个字符每种最多连续重复三次,一旦需要表达四个连续的含义,应使用对应位置上的5倍字符减去它们的形式来进行表达:例如,罗马数字中将44表达为XLVI,意味着X-L=40,V-I=4
  • 利用下一位减去当前位的方式来表达9的概念:90被写成CX,C-X=90,900被写成MC,M-C=900
  • 表达5的概念的字符永远不重复,因为LL代表100,可以直接使用C表达;
  • 罗马数字要从左向右读。字符的顺序对含义的影响十分大,例如DC代表600,而CD代表400;同时,CI代表101,而IC则是一种非法表示,因为罗马数字中的减法不能隔位进行,所以要想表达99的概念必须使用XCIX

匹配合法的千位表示

这里暂时只匹配1000,2000,3000的合法表示:

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

这里唯一需要解释的就是'?'的使用,用以说明'?'前的字符是可选的。这里使用了re.search方法进行匹配,匹配只需要检查返回值是否为None即可,None说明匹配失败。

匹配合法的百位表示

合法的百位表示只有9种:

  • 100 = C
  • 200 = CC
  • 300 = CCC
  • 400 = CD
  • 500 = D
  • 600 = DC
  • 700 = DCC
  • 800 = DCCC
  • 900 = CM

所以可以总结成以下四种形式:

  • CM
  • CD
  • 从字符串首开始0到3个C
  • D, 然后接着0到3个 C

最后两种模式可以结合成:字符串首为可选的'D',紧接着0到3个'C'

>>> import re
>>> pattern = r'^M?M?M?(CM|CD|D?C?C?C?)$'
>>> re.search(pattern, 'MCM')
<_sre.SRE_Match object at 01070390>
>>> re.search(pattern, 'MD')
<_sre.SRE_Match object at 01073A50>
>>> re.search(pattern, 'MMMCCC')
<_sre.SRE_Match object at 010748A8>
>>> re.search(pattern, 'MCMC')
>>> re.search(pattern, '')
<_sre.SRE_Match object at 01071D98>

这里的正则表达式是从上面的任务中改进得到的,最前面和上面的表达式完全相同;第二部分由一个括号组成,括号内代表互斥的三种情况,最后的’$’符号代表了字符串尾。

另一种处理罗马数字的手段——使用{n,m}语法

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

这里使用了'^M{0,3}'的表达式,代表了从字符串首开始匹配0到3个M,可以匹配空串。

匹配罗马数字中的十位和个位数

>>> pattern = '^M?M?M?(CM|CD|D?C?C?C?)(XC|XL|L?X?X?X?)$'
>>> re.search(pattern, 'MCMXL')#MCMXL=1940#
<_sre.SRE_Match object at 0x008EEB48>
>>> re.search(pattern, 'MCML')#MCML=1950#
<_sre.SRE_Match object at 0x008EEB48>
>>> re.search(pattern, 'MCMLX')#MCMLX=1960#
<_sre.SRE_Match object at 0x008EEB48>
>>> re.search(pattern, 'MCMLXXX')#MCMLXXX=1980#
<_sre.SRE_Match object at 0x008EEB48>
>>> re.search(pattern, 'MCMLXXXX')#正确的1990为,MCMXC#
>>> 

若想要匹配个位数,则使用相同的法则即可写出正则表达式:

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

冗长的正则表达式

我们上面讨论的都是紧密型正则表达式,但是这种正则表达式的可读性极差。甚至就算你现在明白正则表达式的含义,也不见得你半年之后还能理解。

python允许你书写冗长正则表达式来解决可读性的问题:

  • 冗长正则表达式中的所有空格,tab字符,换行符都不具有匹配功能,如果你真的希望它们具有匹配功能,你需要对其进行转义;
  • 冗长正则表达式中的所有注释部分不具有匹配功能;
>>> pattern = '''^                   # beginning of stringM{0,3}              # thousands - 0 to 3 Ms(CM|CD|D?C{0,3})    # hundreds - 900 (CM), 400 (CD), 0-300 (0 to 3 Cs),#            or 500-800 (D, followed by 0 to 3 Cs)(XC|XL|L?X{0,3})    # tens - 90 (XC), 40 (XL), 0-30 (0 to 3 Xs),#        or 50-80 (L, followed by 0 to 3 Xs)(IX|IV|V?I{0,3})    # ones - 9 (IX), 4 (IV), 0-3 (0 to 3 Is),#        or 5-8 (V, followed by 0 to 3 Is)$                   # 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')    

注意最后一个示例,‘M’居然无法匹配,为什么?因为方法调用时没有传入re.VERBOSE标记,这样解释器就会把一个冗长正则表达式解读为紧密正则表达式,当然无法生效。

案例3:解析电话号码

作者的一个新任务是将下列形式的电话号码统一化表示:

  • 800-555-1212
  • 800 555 1212
  • 800.555.1212
  • (800) 555-1212
  • 1-800-555-1212
  • 800-555-1212-1234
  • 800-555-1212x1234
  • 800-555-1212 ext. 1234
  • work 1-(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')
>>> 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}则代表恰好三个,括号将上述两种概念合并成一个组。之后要匹配一个短横线,之后再匹配一个三数字组和一个短横线。最后于结尾匹配一个四数字组。

接下来对正则表达式的search方法的调用结果使用groups方法,可以得到groups方法匹配到的所有的结果。但是四组数组成的电话号码就匹配失败了。同时也说明永远不要search().groups(),因为一旦search返回一个None对象,那么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')
>>> 

这里我们仅做出一项改动,把所有的+替换为*,意思是匹配0个或多个非数字字符。但是最后一个测试又无法通过。这次的问题比较简单,只不过是8前面多了一个括号。

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

最后一个测试又失败了,因为我们之前认为800之前出现的字符只能是非数字字符,但是现在出现了一个1。

>>> 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').groups()
('800', '555', '1212', '')
>>> phonePattern.search('80055512121234').groups()
('800', '555', '1212', '1234')

这里我们采取了一种新的手段,不去匹配数字前出现的非数字字符。因为我们根本不需要要求最前面有非数字字符出现。所以去掉^即可实现所有的匹配。

小结

下面我们可以就常见的正则表达式组件进行一个总结和解释了:

  • ^ 匹配字符串的首端;
  • $匹配字符串的末端;
  • \b 匹配一个字符串的边界;
  • \d 匹配任何数字字符;
  • \D 匹配任何非数字字符;
  • x? 可选地匹配一个字符,或者说匹配它0次或者多次;
  • x* 匹配一个字符0次或者多次;
  • x+ 匹配一个字符一次或者多次;
  • x{n,m} 匹配一个字符不少于n次同时也不多于m次;
  • (a|b|c) 仅匹配a, b, c三种模式中的一种;
  • (x) 一般来说记忆匹配到的字符串,通过对re.search返回的对象使用groups方法,可以得到括号内匹配成功的字符串元组;

转载于:https://www.cnblogs.com/Khannia/p/6239343.html

Python中的正则表达式(翻译自DiveintoPython3)相关推荐

  1. Python中re(正则表达式)模块函数学习

    2019独角兽企业重金招聘Python工程师标准>>> Python正则表达式指南 今天学习了Python中有关正则表达式的知识.关于正则表达式的语法,不作过多解释,网上有许多学习的 ...

  2. [Python]网络爬虫(七):Python中的正则表达式教程(转)

    接下来准备用糗百做一个爬虫的小例子. 但是在这之前,先详细的整理一下Python中的正则表达式的相关内容. 正则表达式在Python爬虫中的作用就像是老师点名时用的花名册一样,是必不可少的神兵利器. ...

  3. [Python]网络爬虫(七):Python中的正则表达式教程

    接下来准备用糗百做一个爬虫的小例子. 但是在这之前,先详细的整理一下Python中的正则表达式的相关内容. 正则表达式在Python爬虫中的作用就像是老师点名时用的花名册一样,是必不可少的神兵利器. ...

  4. python中的正则表达式是干嘛的_python中正则表达式总结

    re.match():从字符串的开始进行匹配 re.search():扫描整个字符串返回第一个匹配 re.findAll():返回所有匹配的字符串 Pattern 对象不能实例化,只能通过compil ...

  5. Python中利用正则表达式做数据清洗(re)

    目录 1.常用正则表达式 Python中常用正则表达式 2.正则表达式做数据清洗 2.1 从网页HTML标签中提取文本 2.2 去掉英文文章中标点符号,提取词汇 2.3 提取以.com结尾的邮箱 1. ...

  6. 站长在线零基础Python完全自学教程20:在Python中使用正则表达式完全解读

    欢迎你来到站长学堂,学习站长在线出品的在线课程<零基础 Python完全自学教程>今天给大家分享的是第20课< 在Python中使用正则表达式完全解读>.本节课是一个大课,我分 ...

  7. 站长在线Python精讲:在Python中使用正则表达式的sub()方法替换字符串详解

    欢迎你来到站长在线的站长学堂学习Python知识,本文学习的是<在Python中使用正则表达式的sub()方法替换字符串详解>. 在Python中使用正则表达式的sub()方法替换字符串. ...

  8. 站长在线Python精讲:在Python中使用正则表达式的split()方法分割字符串详解

    欢迎你来到站长在线的站长学堂学习Python知识,本文学习的是<在Python中使用正则表达式的split()方法分割字符串详解>. 使用正则表达式分割字符串 在Python中使用正则表达 ...

  9. python中的正则表达式语法_Python基础教程之正则表达式基本语法以及re模块

    什么是正则: 正则表达式是可以匹配文本片段的模式. 正则表达式'Python'可以匹配'python' 正则是个很牛逼的东西,python中当然也不会缺少. 所以今天的Python就跟大家一起讨论一下 ...

最新文章

  1. 浅谈C中的malloc和free
  2. python趣味编程100例-Python趣味编程与精彩实例,码高少儿编程 编
  3. Swift - 使用下划线(_)来分隔数值中的数字
  4. java 定时任务表达式(网络总结)
  5. 新宝资讯上证涨跌比:1131:649
  6. 【存档】双向可控硅的工作原理
  7. 蓝桥ROS机器人之turtlesim贪吃蛇
  8. Sound Of Firmware 学习 (一) 注册SOF
  9. yar php使用,使用Yar 实现RPC框架
  10. 前度字符串转数组_leetcode每日一题
  11. 极客日报:苹果或推出粉色款iPhone 13;拼多多再超阿里
  12. 新浪微博php实习生电面
  13. hping3的简单介绍(新手向,大佬勿喷)
  14. 【微信小程序】获取用户信息
  15. 神经网络 - BP神经网络与RBF神经网络模型解决实际问题 - (Matlab建模)
  16. 3dmax2010软件下载3dmax2010安装教程
  17. .NET 图片格式转换
  18. 计算机与科技小故事,科学童话小故事大全【三篇】
  19. Jmeter压测输出可观报告--用表格察看结果(view results in table)输出excel格式
  20. VRay 2.0 for SketchUp高级室内渲染教程荟

热门文章

  1. [iOS]将数字、日期转为中文
  2. linux sed替换大小写,linux sed 批量替换字符串
  3. Spark分布式计算原理
  4. python sklearn PCA 实例-主成分分析
  5. “6元麻辣烫事件”中解析六个网络营销方案
  6. 傅里叶变换尺度变换性质_图像处理之傅里叶变换
  7. 中职计算机校企合作人才培训方案,中职计算机专业的校企合作办学模式
  8. IP Source Guard
  9. 发布一个久违的“泡泡堂”单机v2版
  10. python提取选中文件的文件名_如何从python文件路径中提取文件名?