深入Python3(四) 正则表达式

0.摘要

  初步了解 P y t h o n 3 Python3 Python3中的正则表达式。

1.案例研究: 街道地址

在这里插入图片描述

  我的目的是要标准化街道的格式,比如把街道最后的英文单词 R O A D ROAD ROAD替换为 R D . RD. RD.。在第一个例子中使用 r e p l a c e ( ) replace() replace()就可以了。
  但是有些街道名中 R O A D ROAD ROAD出现了多次,比如第二个例子,这时候替换得到的结果是错误的。
  为了解决上面这个问题,你可能会这么考虑:只在地址的最后四个字符中替换 R O A D ROAD ROAD s [ − 4 : ] s[-4:] s[4:])。然后把剩下的字符串独立开来处理( s [ : − 4 ] s[:-4] s[:4])。这个方法很笨拙。比如,这个方法会依赖于你要替换的字符串长度(如果你用 . S T .ST .ST来替换 S T R E E T STREET STREET,就需要在 s [ − 6 : ] s[-6:] s[6:]中查找 S T R E E T STREET STREET,然后再取 s [ : − 6 ] s[:-6] s[:6]。你难道还想半年后回来继续修改BUG?反正我是不想。
  是时候转换到正则表达式了。在 p y t h o n python python中,所有的正则表达式相关功能都包含在 r e re re模块中。注意第一个参数最后的美元符号,这是一个匹配 R O A D ROAD ROAD仅仅出现在字符串结尾的正则表达式。表示字符串结尾(还有一个相应的表示字符串开头的字符 ^ )。正则表达式模块的 r e . s u b ( ) re.sub() re.sub()函数可以做字符串替换,它在字符串 s s s中用正则表达式 R O A D $ ROAD$ ROAD来搜索并替换成 R D . RD. RD.。它只会匹配字符串结尾的 R O A D ROAD ROAD,而不会匹配到 B R O A D BROAD BROAD中的 R O A D ROAD ROAD,因为这种情况它在字符串的中间。
  但是如果有些地址的结尾没有 R O A D ROAD ROAD的话,还是会出现问题:
在这里插入图片描述

  我真正想要的 R O A D ROAD ROAD,必须是匹配到字符串结尾,并且是独立的词(他不能是某个比较长的词的一部分)。为了在正则表达式中表达这个独立的词,你可以使用“\b”(匹配单词的边界)。它的意思是在此处必须有一个分隔符。在 p y t h o n python python中,比较复杂的是“\”字符必须被转义,这有的时候会导致“\”字符传染(想想可能还要对“\”字符做转义的情况)。
  为了解决这个问题,可以使用原始字符串。这只需要在字符串的前面添加一个字符"r"。它告诉 p y t h o n python python,字符串中没有任何字符需要转义。‘\t’是一个制表符,但r\t’只是一个字符‘\’紧跟着一个字符t。我建议在处理正则表达式的时候总是使用原始字符串。否则,会因为理解正则表达式而消耗大量时间(本身正则表达式就已经够让人困惑的了)。
  哎,不幸的是,我发现了更多的地方与我的逻辑背道而驰。街道地址包含了独立的单词ROAD,但并不是在字符串尾,因为街道后面还有个单元号。因为ROAD不在末尾,所以不能匹配,因此 r e . s u b ( ) re.sub() re.sub()最后没有做任何的替换,只是返回了一个原始的字符串,这并不是我想要的。
  为了解决这个问题,我删除了正则表达式尾部的$,然后添加了一个\b。现在这个正则表达式的意思是:在字符串的任意位置匹配独立的ROAD单词,不管是在字符串的结束还是开始,或者中间的任意一个位置。

2.案例研究: 罗马数字

在这里插入图片描述

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

2.1检查千位数

  怎么验证一个字符串是否是一个合法的罗马数字呢?我们可以每次取一个字符来处理。因为罗马数字总是从高位到低位来书写。我们从最高位的千位开始。表示1000或者更高的位数值,方法是用一系列的M来重复表示。
在这里插入图片描述

  这个模式有三部分。^表示必须从字符串开头匹配; M ? M? M?表示匹配 M M M 1次或0次;$匹配字符串结束。所以这个模式可以匹配字符串 M 、 M M 、 M M M M、MM、MMM MMMMMM或空串。
   r e re re模块最基本的方法是 s e a r c h ( ) search() search()函数。它使用正则表达式来匹配字符串。如果成功匹配, s e a r c h ( ) search() search()返回一个匹配对象。匹配对象中有很多的方法来描述这个匹配结果信息。如果没有匹配到, s e a r c h ( ) search() search()返回 N o n e None None。你只需要关注 s e a r c h ( ) search() search()函数的返回值就可以知道是否匹配成功。

2.2检查百位数

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  这个正则表达式的写法从上面千位的匹配方法接着往后写。检查字符串开始(^),然后是千位,后面才是新的部分。这里用圆括号定义了三个不同的匹配模式,他们是用竖线分隔的:CM,CD和D?C?C?C?(这表示是一个可选的D,以及紧跟的0到3个可选的字符C)。正则表达式按从左到右的顺序依次匹配,如果第一个CM匹配成功,用竖线分隔这几个中的后面其他的都会被忽略(相当于逻辑运算符||)。

3.使用语法{n,m}

  在上一节中,你处理过同样的字符可以重复0到3次的情况。实际上,还有另一种正则表达式的书写方式可以表达同样的意思,而且这种表达方式更具有可读性。首先看看我们在前面例子中使用的方法。
在这里插入图片描述

  现在使用 { n , m } \{n,m\} {n,m}语法:
在这里插入图片描述

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

3.1检查十位和个位

  首先我们要清楚十位和个位都有哪些表示。

  • 10 = X 10=X 10=X
  • 20 = X X 20=XX 20=XX
  • 30 = X X X 30=XXX 30=XXX
  • 40 = X L 40=XL 40=XL
  • 50 = L 50=L 50=L
  • 60 = L X 60=LX 60=LX
  • 70 = L X X 70=LXX 70=LXX
  • 80 = L X X X 80=LXXX 80=LXXX
  • 90 = X C 90=XC 90=XC

  那么十位的可以表示为: ( X C ∣ X L ∣ L ? X { 0 , 3 } ) (XC|XL|L?X\{0,3\}) (XCXLL?X{0,3})

  • 1 = I 1=I 1=I
  • 2 = I I 2=II 2=II
  • 3 = I I I 3=III 3=III
  • 4 = I V 4=IV 4=IV
  • 5 = V 5=V 5=V
  • 6 = V I 6=VI 6=VI
  • 7 = V I I 7=VII 7=VII
  • 8 = V I I 8=VII 8=VII
  • 9 = I X 9=IX 9=IX

  个位的可以表示为 ( I X ∣ I V ∣ V ? I { 0 , 3 } ) (IX|IV|V?I\{0,3\}) (IXIVV?I{0,3})。结合之前的模式,我们可以得出最终结果:
在这里插入图片描述

4.松散正则表达式

  现在我们来研究一下怎么让你的正则表达式更具有维护性,但表达的意思却是相同的。到目前为止,你只是处理了一些小型的正则表达式。就像你所看到的,他们难以阅读,甚至你不能保证半年后,你还能理解这些东西,并指出他们是干什么的。所以你需要在正则表达式内部添加一些说明信息。
   p y t h o n python python允许你使用松散正字表达式来达到目的。松散正字表达式和普通紧凑的正则表达式有两点不同:

  • 空白符被忽略。空格、制表符和回车在正则表达式中并不会匹配空格、制表符、回车。如果你想在正则表达式中匹配他们,可以在前面加一个\来转义。
  • 注释信息被忽略。松散正则表达式中的注释和 p y t h o n python python代码中的一样,都是以#开头直到行尾。它可以在多行正则表达式中增加注释信息,这就避免了在python代码中的多行注释。他们的工作方式是一样的。
    在这里插入图片描述

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

5.案例研究: 解析电话号码

  到目前为止,我们主要关注于整个表达式是否能匹配到,要么整个匹配,要么整个都不匹配。但正则表达式还有更加强大的功能。如果正则表达式成功匹配,你可以找到正则表达式中某一部分匹配到什么。
在这里插入图片描述
在这里插入图片描述

  我们通常从左到右的阅读正则表达式。首先是匹配字符串开始位置,然后是(\d{3})。\d{3}表示什么意思?\d表示任意的数字(0到9),{3}表示一定要匹配3个数字。这个是你前面看到的{n,m}表示方法。把他们放在圆括号中,表示必须匹配3个数字,并且把他们记做一个组。分组的概念我们后面会说到。然后匹配一个连字符,接着匹配另外的3个数字,他们也同样作为一个组。然后又是一个连字符,后面还要准确匹配4个数字,他们也作为一位分组。最后匹配字符串结尾。
  为了使用正则表达式匹配到的这些分组,需要对 s e a r c h ( ) search() search()函数的返回值调用 g r o u p s ( ) groups() groups()方法。它会返回一个这个正则表达式中定义的所有分组结果组成的元组。在这里,我们定义了三个分组,一个三个数字,另一个是三个数字,以及一个四个数字。
  请注意,不要链式调用 s e a r c h ( ) search() search() g r o u p s ( ) groups() groups()。因为前者的结果可能是 N o n e None None
在这里插入图片描述

  这个正则表达式和前面的一样。匹配了字符串开始位置,然后是一个三个数字的分组,接着一个连字符,又是一个三个数字的分组,又是一个连字符,然后一个四个数字的分组。这三个分组匹配的内容都会被记忆下来。和上面不同的是,这里多匹配了一个连字符以及一个分组,这个分组里的内容是匹配一个或更多个数字。最后是字符串结尾。
  现在 g r o u p s ( ) groups() groups()方法返回有四个元素的元组。因为正则表达式现在定义了四个组。
  不幸的是,这个正则表达式仍然不是最终答案。因为它假设这些数字是有连字符分隔的。实际上还有用空格,逗号和点分隔的情况。这就需要用更加通用的解决方案来匹配这些不同的分隔符。
  噢,这个正则表达式不但不能做到你想要的,而且还不如上一个了!因为我们现在不能匹配没有分机号的电话号码,这绝对不是你想要的。
在这里插入图片描述

  注意了!你匹配了字符串开始,然后是3个数字的分组,接着是\D+,这是什么?好吧,\D匹配除了数字以外的任意字符,+的意思是一个或多个。因此\D+匹配一个或一个以上的非数字字符。这就是你用来替换连字符的东西,它用来匹配不同的分隔符。
  所以当分隔符为空格、连字符或其它的字符时,依然可以匹配成功。但是还有一些写问题需要解决,比如没有分隔符时,没有分机号时。
在这里插入图片描述

  这里和上面唯一不同的地方是,把所有的+换成了*。号码之间的分隔符不再用\D+来匹配,而是使用\D*。+表示1个或多个,*表示0个或多个。
  所以当没有分隔符或者没有分机号时,它依然可以工作。注意当没有分机号时,第四个字符串是空的。
  这还没有结束。还有什么问题呢?在区域码前面还可能有其他字符。但正则表达式假设区域码在字符串的开头,没关系,你还可以使用0个或更多的非数字字符串来跳过区位码前面的字符。
在这里插入图片描述

  只需要在第一个分组之前加上\D*来匹配0个或多个非数字字符即可。注意你不会对这些非数字字符分组,因为他们不在圆括号内,也就是说不是一个组。如果发现有这些字符,这里只是跳过他们,然后开始对后面的区域码匹配、分组。
  但是如果在第一个分组之前有数字字符,比如第4个例子,我们还是无法成功匹配。因为\D*并不能匹配数字字符。
  到目前为止,所有的正则表达式都匹配了字符串开始位置。但现在在字符串的开头可能有一些你想忽略掉的不确定的字符。为了匹配到想要的数据,你需要跳过他们。我们来看看不明确匹配字符串开始的方法。
在这里插入图片描述

  注意正则表达式没有^。不会再匹配字符串开始位置了。正则表达式不会匹配整个字符串,而是试图找到一个字符串开始匹配的位置,然后从这个位置开始匹配。
在这里插入图片描述

6.小结

  这只是正则表达式能完成的工作中的冰山一角。换句话说,尽管你可能很受打击,相信我,你已经不是什么都不知道了。
  现在,你应该已经熟悉了下面的技巧:
在这里插入图片描述

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值