6.2.2 使用re模块处理正则表达式

Python的re模块具有正则表达式匹配的功能。re模块提供了一些根据正则表达式进行查找、替换、分隔字符串的函数,这些函数使用一个正则表达式作为第一个参数。re模块常用的函数如表6-7所示。

表6-7 re模块的常用函数

注意 函数match()必须从字符串的第0个索引位置处开始搜索。如果第0个索引位置的字符不匹配,match()的匹配失败。

re模块的一些函数中都有一个flags参数,该参数用于设置匹配的附加选项。例如,是否忽略大小写、是否支持多行匹配等。表6-8列出了re模块的规则选项。

表6-8 re模块的规则选项

re模块定义了一些常量来表示这些选项,使用前导符“re.”加选项的简写或名称的方式表示某个常量。例如,re.I或re.IGNORECASE表示忽略大小写。

正则表达式中有3种间隔符号:“^”“$”和“\b”。“^”匹配字符串首部的子串,“$”匹配结束部分的子串,而“\b”用于分隔单词。下面这段代码展示了这些间隔符在Python中的使用。


01     import re                    # 导入re模块
02     # ^与$的使用
03     s = "HELLO WORLD"
04     print (re.findall(r"^hello", s))
05     print (re.findall(r"^hello", s, re.I))
06     print (re.findall("WORLD$", s))
07     print (re.findall(r"wORld$", s, re.I))
08     print (re.findall(r"\b\w+\b", s))

【代码说明】

·第4行代码匹配以“hello”开始的字符串。由于变量s中的“HELLO”采用的是大写,所有匹配失败。输出结果为“[]”。

·第5行代码添加了辅助参数flags,re.I表示匹配时忽略大小写。输出结果为“['HELLO']”。

·第6行代码匹配以“WORLD”结尾的字符串。输出结果为“['WORLD']”。

·第7行代码匹配以“WORLD”结尾的字符串,并忽略大小写。输出结果为“['WORLD']”。

·第8行代码匹配每个英文单词。输出结果为“['HELLO','WORLD']”。

前面介绍了可用replace()实现字符串的替换,同样可以使用re模块的sub()实现替换的功能。下面这段代码演示了用sub()替换字符串。


01     import re                    # 导入re模块
02
03     s = "hello world"
04     print (re.sub("hello", "hi", s))
05     print (re.sub("hello", "hi", s[-4:]))
06     print (re.sub("world", "China", s[-5:]))

【代码说明】

·第4行代码的输出结果为“hi world”。

·第5行代码在分片s[-4:]范围内替换“hello”,即在字符串“orld”中替换“hello”。由于没有找到匹配的子串,所以sub()返回s[-4:]。输出结果为“orld”。

·第6行代码在分片s[-5:]范围内替换“world”,即把字符串“world”替换为“China”。输出结果为“China”。

注意 sub()先创建变量s的拷贝,然后在拷贝中替换字符串,并不会改变变量s的内容。

subn()的功能与sub()相同,但是多返回1个值,即匹配后的替换次数。【例6-2】中的这段代码演示了subn()对字符串的替换以及正则表达式中特殊字符的使用。

【例6-2.py】


01     import re               # 导入re模块
02     # 特殊字符的使用
03     s = "你好 WORLD2"
04     print ("匹配字母、数字、下划线、汉字字符:" + re.sub(r"\w", "hi", s))
05     print ("替换次数:" + str(re.subn(r"\w", "hi", s)[1]))
06     print ("匹配非字母、数字、下划线、汉字的字符:" + re.sub(r"\W", "hi", s))
07     print ("替换次数:" + str(re.subn(r"\W", "hi", s)[1]))
08     print ("匹配空白字符:" + re.sub(r"\s", "*", s))
09     print ("替换次数:" + str(re.subn(r"\s", "*", s)[1]))
10     print ("匹配非空白字符:" + re.sub(r"\S", "hi", s))
11     print ("替换次数:" + str(re.subn(r"\S", "hi", s) [1]))
12     print ("匹配数字:" + re.sub(r"\d", "2.0", s))
13     print ("替换次数:" + str(re.subn(r"\d", "2.0", s)[1]))
14     print ("匹配非数字:" + re.sub(r"\D", "hi", s))
15     print ("替换次数:" + str(re.subn(r"\D", "hi", s)[1]))
16     print ("匹配任意字符:" + re.sub(r".", "hi", s))
17     print ("替换次数:" + str(re.subn(r".", "hi", s)[1]))

【代码说明】

·第4行代码,“\w”匹配字母、数字、下划线、汉字。输出结果:


匹配字母、数字、下划线、汉字字符: hihihihihihihihi

·第5行代码输出替换次数。替换次数存放在subn()返回的元组的第2个元素中。字符串“你好WORLD2”有8个字符,所以被替换为8个“hi”。输出结果:


替换次数:8

·第6行代码替换非字母、数字、下划线、汉字的字符。输出结果:


匹配非字母、数字、下划线、汉字的字符:你好hiWORLD2

·第7行代码,后面的空格被替换为1个“hi”。输出结果:


替换次数:1

·第8行代码匹配空白字符,空格、制表符等都属于空白字符。输出结果:


匹配空白字符:你好*WORLD2

·第9行代码,由于只有一个空格,所以替换次数为1。输出结果:


替换次数:1

·第10行代码替换非空白字符。输出结果:


匹配非空白字符:pypy pypypypypypy

·第11行代码,除空白外字符串共占用了8个字符。输出结果:


替换次数:8

·第12行代码替换数字。输出结果:


匹配数字:你好 WORLD2.0

·第13行代码把数字2替换为“2.0”。输出结果:


替换次数:1

·第14行代码替换每个英文字符。输出结果:


匹配非数字:pypypypypypypypy2

·第15行代码替换了8个非数字字符。输出结果:


替换次数:8

·第16行代码,用“.”替换任意字符。输出结果:


匹配任意字符:pypypypypypypypypy

·第17行代码,所有9个字符均被替换。输出结果:


替换次数:9

前面提到了解析电话号码的正则表达式,下面通过Python程序来实现电话号码的匹配。


01     import re               # 导入re模块
02     # 限定符的使用
03     tel1 = "0791-1234567"
04     print (re.findall(r"\d{3}-\d{8}|\d{4}-\d{7}", tel1))
05     tel2 = "010-12345678"
06     print (re.findall(r"\d{3}-\d{8}|\d{4}-\d{7}", tel2))
07     tel3 = "(010)12345678"
08     print (re.findall(r"[\(]?\d{3}[\) -]?\d{8}|[\(]?\d{4}[\) -]?\d{7}", tel3))

【代码说明】

·第4行代码匹配区号为3位的8位数电话号码或区号为4位的7位数电话号码。输出结果为“['0791-1234567']”。

·第6行代码匹配区号为3位的8位数电话号码或区号为4位的7位数电话号码,区号和电话号码之间用“-”连接。输出结果为“['010-12345678']”。

·第8行代码匹配区号为3位的8位数电话号码或区号为4位的7位数电话号码,区号和电话号码之间可以采用3种方式书写。一是直接使用“-”连接,二是在区号两侧添加圆括号再连接电话号码,三是区号和电话号码连在一起书写。输出结果为“['(010)12345678']”。

正则表达式的解析非常费时。如果多次使用findall()的方式匹配字符串,搜索效率可能比较低。如果多次使用同一规则匹配字符串,可以使用compile()进行预编译,compile()函数返回1个pattern对象。该对象拥有一系列用于查找、替换或扩展字符串的方法,从而提高了字符串的匹配速度。表6-9列出了pattern对象的属性和方法。

表6-9 pattern对象的属性和方法

下面这段代码在1个字符串中查找多个数字,使用compile()提高查找的效率。


01     import re               # 导入re模块
02     # compile()预编译
03     s = "1abc23def45"
04     p = re.compile(r"\d+")
05     print (p.findall(s))
06     print (p.pattern)

【代码说明】

·第4行代码返回1个正则表达式对象p,匹配变量s中的数字。

·第5行代码调用p的findall()方法,匹配的结果存放在列表中。输出结果为“['1','23','45']”。

·第6行代码输出当前使用的正则表达式。输出结果为“\d+”。

函数compile()通常与match()、search()、group()一起使用,对含有分组的正则表达式进行解析。正则表达式的分组从左往右开始计数,第1个出现的圆括号标记为第1组,以此类推。此外还有0号组,0号组用于存储匹配整个正则表达式的结果。match()和search()将返回1个match对象,match对象提供了一系列的方法和属性来管理匹配的结果。表6-10列出了match对象的方法和属性。

表6-10 match对象的方法和属性

下面【例6-3】中的这段代码演示了对正则表达式分组的解析。

【例6-3.py】


01     import re                    # 导入re模块
02     # 分组
03     p = re.compile(r"(abc)\1")
04     m = p.match("abcabcabc")
05     print (m.group(0))
06     print(m.group(1))
07     print (m.group())
08
09     p = re.compile(r"(?P<one>abc)(?P=one)")
10     m = p.search("abcabcabc")
11     print (m.group("one"))
12     print (m.groupdict().keys())
13     print (m.groupdict().values())
14     print (m.re.pattern)

【代码说明】

·第3行代码定义了1个分组“(abc)”,在后面使用“\1”再次调用该分组。即compile()返回1个包含2个分组的正则表达式对象p。

·第4行代码,p.match()对字符串“abcabcab”进行搜索,返回1个match对象m。

·第5行代码调用match对象的group(0)方法,匹配0号组。输出结果为“abcabc”。

·第6行代码调用match对象的group(1)方法,匹配1号组。输出结果为“abc”。

·第7行代码,默认情况下,返回分组0的结果。输出结果为“abcabc”。

·第9行代码,给分组命名,“?P<one>”中的“one”表示分组的名称。“(?P=one)”调用分组“one”,相当于“\1”。

·第11行代码输出分组“one”的结果。输出结果为“abc”。

·第12行代码获取正则表达式中分组的名称。输出结果为“dict_keys(['one'])”。

·第13行代码获取正则表达式中分组的内容。输出结果为“dict_values(['abc'])”。

·第14行代码获取当前使用的正则表达式。输出结果为“(?P<one>abc)(?P=one)”。

如果使用match()匹配的源字符串“abcabcabc”改为“bcabcabc”,则Python将提示如下错误。


AttributeError: 'NoneType' object has no attribute 'group'

对于这种情况,可以用search()替换match(),search()可以匹配出正确的结果。