- 零基础学Python(第2版)
- 黄传禄 张克强 赵越
- 2970字
- 2025-03-22 17:27:10
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()可以匹配出正确的结果。