part10-3 Python常见模块(正则表达式)
六、 Python 正则表达式
正则表达式(Regular Expression)用于描述一种字符串匹配的模式(Pattern),即可用于检查一个字符串是否含有某个子串,也可用于从字符串中提取匹配到的子串,或者对字符串中匹配到的子串执行替换操作。
正则表达式是一个非常实用的工具,它包含的知识点较多,它的模式匹配能力也非常强,学习需要由浅入深的学习。
熟练使用正则表达式是一个很重要的技能。可用正则表达式来开发数据抓取、网络爬虫等程序。在 Python 中的正则表达式就是几个常用函数,难点是正则表达式字符串的开发。
1、 Python 的正则表达式支持
导入 re 模块后,可使用 re.__all__ 命令查看该模块所包含的全部属性和函数。示例如下:
>>> import re
>>> re.__all__
['match', 'fullmatch', 'search', 'sub', 'subn', 'split', 'findall', 'finditer', 'compile', 'purge', 'template', 'escape', 'error', 'A', 'I', 'L', 'M', 'S', 'X', 'U', 'ASCII', 'IGNORECASE', 'LOCALE', 'MULTILINE', 'DOTALL', 'VERBOSE', 'UNICODE']
re 模块中的常用函数介绍:
(1)、re.compile(pattern,flags=0):该函数用于将 pattern 代表的正则表达式字符串编译成 _sre.SRE_Pattern 对象,该对象是正则表达式编译之后在内存中的对象,编译后的对象可以缓存并复用正则表达式字符串。在代码中如果需要多次使用同一个正则表达式字符串,则可以先编译后再使用,这样可提高运行效率。
参数 flags 表示正则表达式的旗标。经过编译后的 _sre.SRE_Pattern 对象可以调用 re 模块中大部分函数。例如下面代码所示,将编译后的 _sre.SRE_Pattern 对象调用 re 模块的 search() 方法进行匹配。
import re # 先编译正则表达式 p = re.compile('abc') # 调用 _sre.SRE_Pattern 对象的 search() 方法 进行匹配 print(p.search('www.abc.com')) # 下面代码直接使用 re 模块的 search() 函数匹配目标字符串 print(re.search('abc', 'www.abc.com')) 运行结果如下所示: <_sre.SRE_Match object; span=(4, 7), match='abc'> <_sre.SRE_Match object; span=(4, 7), match='abc'>
从输出可以看到,两次调用 search() 函数匹配到的结果是一样的,但是第一种方式预编译了正则表达式,可以复用 p 对象(该对象缓存了正则表达式字符串),有更好的性能。
(2)、re.match(pattern,string,flags=0):根据 pattern 的正则模式从 string 字符串的开始位置进行匹配。如果从开始位置匹配不成功,match() 函数就返回 None。flags 参数代表正则表达式的匹配旗标。该函数返回 _sre.SRE_Match 对象,该对象包含的 span(n) 方法用于获取第 n+1 个组的匹配位置,group(n) 方法用于获取第 n+1 个组所匹配的子串。
(3)、re.search(pattern, string, flags=0):根据 pattern 的正则模式在 string 代表的字符串中进行扫描,并返回字符串中第一处匹配 pattern 的匹配对象。flags 参数同样代表正则表达式的匹配旗标。该函数也返回 _sre.SRE_Match 对象。
match() 与 search() 的区别在于:match() 必须从字符串开始处就匹配,但 search() 可以搜索整个字符串。示例如下:
import re m1 = re.match('www', 'www.michael.com') # 从字符串开始处匹配 print(m1.span()) # span 返回匹配的位置,(0, 3) print(m1.group()) # group 返回匹配的组,www print(re.match('mich', 'www.mich.com')) # 如果从开始位置匹配不到,返回 None m2 = re.search('www', 'www.michael.com') # 从开始位置匹配 print(m2.span()) # (0, 3) print(m2.group()) # www m3 = re.search('mich', 'www.michael.com') # 从中间位置开始匹配,返回 Match 对象 print(m3.span()) # (4, 8) print(m3.group()) # mich 运行代码,输出结果如下: (0, 3) www None (0, 3) www (4, 8) mich
从上面代码的输出可知,match() 函数要求必须从字符串开始处匹配,而 search() 函数是扫描整个字符串,可以从字符串的任意位置开始匹配。
(4)、re.findall(pattern, string, flags=0):根据 pattern 的匹配模式对 string 字符串整个扫描,并返回字符串中所有与pattern 模式匹配的子串组成的列表。flags 参数同样是正则表达式的匹配旗标。
(5)、re.finditer(pattern, string, flags=0):根据 pattern 的匹配模式对 string 字符串整个扫描,并返回字符串中所有与 pattern 模式匹配的子串组成的迭代器,迭代器的元素是 _sre.SRE_Match 对象。flags 参数同样是正则表达式的匹配旗标。
findall() 和 finditer() 函数的功能基本相同,不同的是在于它们的返回值,findall() 返回所有匹配 pattern 的子串组成的
列表;而 finditer() 返回所有匹配 pattern 的子串组成的迭代器。
另外,search() 与 findall()、finditer() 的区别是,search() 只返回字符串中第一个匹配 pattern 的子串;而 findall() 和 finditer() 则返回字符串所有匹配 pattern 的子串。
findall() 和 finditer() 的使用示例如下:
import re # 返回所有匹配 pattern 的子串组成的列表,re.I 旗标表示忽略大小写 print(re.findall('py', 'Py is very good, Py.org is official website', re.I)) # 返回所有匹配 pattern 的子串组成的迭代器,忽略大小写 it = re.finditer('py', 'Py is very good, Py.org is official website', re.I) for i in it: print(str(i.span()) + "-->" + i.group()) 运行代码,输出结果如下: ['Py', 'Py'] (0, 2)-->Py (17, 19)-->Py
(6)、 re.fullmatch(pattern, string, flags=0):该函数要求整个string字符串能匹配 pattern,如果匹配则返回包含匹配信息的 _sre.SRE_Match 对象;否则返回 None。
(7)、re.sub(pattern, repl, string, count=0, flags=0):该函数用于将 string 字符串所有匹配 pattern 的内容替换成 repl;repl 可以是字符串,也可以是一个函数。count 参数控制最多替换多少次,如果指定 count为0,则表示全部替换。
sub() 函数的用法示例如下:
import re my_date = '2019-11-11' # 将 my_date 字符串中的 - 替换为 / print(re.sub(r'-', '/', my_date)) # 只做一次替换 print(re.sub(r'-', '/', my_date, 1)) 输出如下所示: 2019/11/11 2019/11-11
上面代码中的 r'-' 是原始字符串,r 代表原始字符串,可以避免对字符串中的特殊字符进行转义。sub() 在执行替换时可以基于被替换的内容进行改变。例如下面代码将字符串中的每个英文单词都变成一本图书的名字。示例如下:
import re # 在匹配的字符串前后添加内容 def func(matched): # matched 就是匹配对象,通过该对象的 group() 方法可以获取被匹配的字符串 result = '《' + matched.group('lang') + "入门到高级》" return result s = 'Python 很好, Linux 也很好' # 对 s 里面的英文单词进行替换,用 re.A 旗标控制单词 print(re.sub(r'(?P<lang>\w+)', func, s, flags=re.A)) 运行结果如下所示: 《Python入门到高级》 很好, 《Linux入门到高级》 也很好
从输出结果可以看出,使用 sub() 函数执行替换时,指定使用 func() 函数作为替换内容,而 func() 函数负责在 pattern 匹配的字符串之前添加 “《”,在 pattern 匹配的字符串之后添加 “入门到高级》”。所以会看到上面的输出结果。
r'(?P<lang>\w+)' 正则表达式用圆括号表达式创建了一个组,并使用 “?P” 选项为该组起名为 lang,所起的组名要放在尖括号内。剩下的 “\w+” 才是正则表达式的内容,其中 “\w” 代表任意字符;“+” 限定前面的 “\w” 可出现一次到多次,因此,“\w+” 代表一个或多个任意字符。由于在后面指定的 re.A 选项,这样 “\w” 就只能匹配 ASCII 字符,不能匹配汉字。
在使用 sub() 函数执行替换时正则表达式 “\w+” 所匹配的内容可以通过组名 “lang” 来获取,这样 func() 函数就调用了 matched.group('lang') 来获取 “\w+” 所匹配的内容。
(8)、re.split(pattern, string, maxsplit=0, flags=0):使用 pattern 对 string 进行分割,该函数返回分割得到的多个子串组成的列表。maxsplit 参数控制最多分割多少次,默认全部分割。
split() 函数用法示例如下:
import re # 使用逗号对字符串进行分割 print(re.split(', ', 'python, linux, java')) # 输出:['python', 'linux', 'java'] # 指定只分割一次,被分割成两个子串 print(re.split(', ', 'python, linux, java', 1)) # 输出:['python', 'linux, java'] # 使用 n 进行分割,未匹配成功时就不分割,比如使用 b 字符进行分割 print(re.split('n', 'python, linux, java')) # 输出:['pytho', ', li', 'ux, java']
(9)、re.purge():清除正则表达式缓存。
(10)、re.excape(pattern):对模式中除 ASCII 字符、数值、下划线(_)之外的其他字符进行转义。也就是对模式中的特殊字符进行转义。
escape() 函数的用法示例如下:
import re # 对模式中的特殊字符进行转义 print(re.escape(r'www.michael.com is very strong, I like it! 666')) # 输出:www\.michael\.com\ is\ very\ strong\,\ I\ like\ it\!\ 666 print(re.escape(r'A-Zand0-9?')) # 输出:A\-Zand0\-9\?
从上面代码的输出结果可知,escape() 函数对非 ASCII 字符、数值、下划线(_)之外的其他字符都进行了转义。
在 re 模块中还包含有两个类,分别是正则表达式对象(类型是 _sre.SRE_Pattern)和匹配(Match)对象。其中正则表达式对象是调用 re.compile() 函数的返回值,该对象的方法与 re 模块中的函数大致对应。但是正则表达对象的 search()、match()、fullmatch()、findall()、finditer()方法的功能要强大一些,因为这些方法可以额外指定 pos 和 endpos 两个参数,用于指定只处理目标字符串从 pos 开始到 endpos 结束之间的子串。下面代码示例用正则表达式对象的方法来执行匹配。代码如下:
import re # 首先编译得到正则表达式对象,从类的角度来看就是在创建对象(或实例)pa pa = re.compile('mich') # 调用 pa 对象的 match 方法,该方法原本是从开始位置匹配的, # 但是在这里可以指定开始匹配的位置,指定从索引 4 开始匹配 print(pa.match('www.michael.com', 4).span()) # (4, 8) # 下面指定索引起始和结束位置,指定索引4 到 6 之间执行匹配,匹配失败 print(pa.match('www.michael.com', 4, 15)) # None # 下面指定从索引 4 到索引 8 之间执行完全匹配,匹配成功 print(pa.fullmatch('www.michael.com', 4, 8).span()) # (4, 8)
上面代码中使用正则表达式对象来调用 match()、fullmatch()方法时指定了 pos 和 endpos 参数,这样可以只处理目标字符串的中间一段。在使用 compile() 函数编译正则表达式后,该函数所返回的对象就会缓存该正则表达式,从而可以多次调用该正则表达式执行匹配。例如上面代码多次使用了 pa 对象来执行匹配。
re 模块中的 Match 对象(类型是 _sre.SRE_Match)是 match()、search() 方法的返回值,该对象中包含了详细的正则表达式匹配信息,包括正则表达匹配的位置、正则表达式匹配的子串。
_sre.SRE_Match 对象(匹配对象)包含的方法和属性有下面这些:
(1)、match.group([group1, ...]):获取该匹配对象中指定组所匹配的字符串。
(2)、match.__getitem__(g):这是 match.group(g) 的简化写法。由于 match 对象提供了 __getitem__() 方法,因此程序可使用 match[g] 来代替 match.group(g)。
(3)、match.groups(default=None):返回 match 对象中所有组所匹配的字符串组成的元组。
(4)、match.groupdict(default=None):返回 match 对象中所有组所匹配的字符串组成的字典。
(5)、match.start([group]):获取该匹配对象中指定组所匹配的字符串的开始位置。
(6)、match.end([group]):获取该匹配对象中指定组所匹配的字符串的结束位置。
(7)、match.span([group]):获取该匹配对象中指定组所匹配的字符串的开始位置和结束位置。该方法相当于同时返回 start() 和 end() 方法的返回值。
上面7个方法都涉及到组,在正则表达式中,用圆括号将多个表达式括起来就形成组。如果正则表达式中没有圆括号,则整个表达式就属于一个默认组。下面代码示例了使用组的情形:
import re # 在正则表达式中使用组 m = re.search(r'(?P<py>python).(?P<o>org)', r'www.python.org is official website') print(m.group(0)) # python.org # 调用简化的写法,底层是调用 m.__getitem__(0) print(m[0]) # python.org print(m.span(0)) # (4, 14) print(m.group(1)) # python # 调用简化的写法,底层是调用 m.__getitem__(1) print(m[1]) # python print(m.span(1)) # (4, 10) print(m.group(2)) # org # 调用简化的写法,底层是调用 m.__getitem__(2) print(m[2]) # org print(m.span(2)) # (11, 14) # 返回所有组所匹配的字符串组成的元组 print(m.groups()) # ('python', 'org') # 要返回字典的情况时,必须要为组起一个名字,名字就是字典的键 print(m.groupdict()) # {'py': 'python', 'o': 'org'} 运行代码,输出结果如下: python.org python.org (4, 14) python python (4, 10) org org (11, 14) ('python', 'org') {'py': 'python', 'o': 'org'}
上面代码中 search() 函数的正则表达式是 r'(?P<py>python).(?P<o>org)',这个正则表达式中有两个组,并且每个组都起了一个名字。接下来的代码中可以依次获取 group(0)、group(1)、group(2)的值,依次获取的是整个正则表达式所匹配的子串、第一个组匹配的子串、第二个组匹配的子串;也可以依次获取 span(0)、span(1)、span(2)的值,依次获取整个正则表达式所匹配子串的开始和结束位置、第一个组匹配子串的开始和结束位置、第二个组匹配子串的开始和结束位置。
只要正则表达式能匹配到结果,则不管正则表达是否包含组,group(0)、span(0)都能获取到内容。其中 group(0) 是获取整个匹配到的整个子串,span(0) 获取到的是整个子串的开始和结束位置。
在正则表达式中,使用 ?P<name> 方式为组指定名字后,就可以调用 _sre.SRE_Match 对象(匹配对象)的 groupdict() 方法获得字典,字典的 key 是为组指定的名字,value 是匹配到的单组结果。
下面继续说下 _sre.SRE_Match 对象(匹配对象)的方法:
(8)、match.pos:该属性返回传给正则表达式对象的 search()、match() 等方法的 pos 参数。
(9)、match.endpos:是传给正则表达式对象的 search()、match()等方法的 endpos 参数值。
(10)、match.lastindex:最后一个正则匹配的捕获组的整数索引。如果没有组匹配,该属性值是 None。例如用 (a)b、((a)(b))或 ((ab)) 对字符串 'ab' 执行匹配,该属性都会返回 1;但如果使用 (a)(b) 正则表达式对 'ab' 执行匹配,则 lastindex 等于 2。
(11)、match.lastgroup:返回最一个匹配的捕获组的名字;如果该组没有名字或根本没有组匹配,该属性返回 None。
(12)、match.re:返回执行正则表达式匹配时所用的正则表达式。
(13)、match.string:返回执行正则表达式匹配时所用的字符串。被匹配的字符串string。
2、 正则表达式旗标
Python 的正则表达式旗标都使用 re 模块中的属性来代表,旗标有下面这些:
(1)、re.A 或 re.ASCII:控制\w,\W,\b,\B,\d,\D,\s,\S 这些特殊符号只匹配 ASCII 字符,而不是匹配所有的 Unicode 字符。也可在正则表达式中使用 (?a) 行为旗标来代表。
(2)、re.DEBUG:显示编译正则表达式的 Debug 信息,没有行为旗标。
(3)、re.I 或 re.IGNORECASE:在匹配时不区分大小写。对应的行为旗标是 (?i)。
re.I 旗标示例如下:
# 默认要区分大小写,所以无匹配结果 >>> import re >>> re.findall(r'pyth', 'Python is good, PYTHON is good') [] # 使用 re.I 旗标后,限定不区分大小写,现在有匹配结果 >>> re.findall(r'pyth', 'Python is good, PYTHON is good', re.I) ['Pyth', 'PYTH'] # 使用行为旗标 (?i) 限定不区分大小写,同样能匹配到结果 >>> re.findall(r'(?i)pyth', 'Python is good, PYTHON is good') ['Pyth', 'PYTH']
(4)、re.L 或 re.LOCALE:根据当前区域设置使用正则表达式匹配时不区分大小写。该旗标只对 bytes 模式起作用,对应的行内旗标是 (?L)。
(5)、re.M 或 re.MULTILINE:匹配多行模式的旗标。使用该旗标后,可以匹配每一行的开头,以及 “^” 符号能匹配字符串的开头;同时 “$” 符号能匹配字符串的末尾和每一行的末尾。没有该旗标的正则表达式,“^” 符号只匹配字符串的开头,“$” 符号只匹配字符串的结尾,或者匹配到字符串默认的换行符(如果有)之前。对应的行为旗标是 (?m)。
(6)、re.S 或 re.DOTALL:指定该旗标后,点(.)符号可以匹配换行符,默认不匹配换行符。行内旗标是 (?s)。
(7)、re.U 或 re.Unicode:控制\w,\W,\b,\B,\d,\D,\s,\S 这些特殊符号能匹配所有的 Unicode 字符。在 Python 3.x 版本中,默认匹配的就是所有 Unicode 字符,所以该旗标基本上没什么用。
(8)、re.X 或 re.VERBOSE:使用该旗标后,正则表达式可以换行,还可以为正则表达式添加注释,提高正则表达式可读性。对应的行内旗标是 (?x)。
re.X 旗标的用法示例如下:
import re a = re.compile(r"""\d{3} # 地方区号 \- # 号码分界线 \d{8} # 匹配8个数字""", re.X) b = re.compile(r'\d{3}\-\d{8}') print(a.search('你好,我成都的电话号码是:028-12345678').group()) print(b.search('你好,我首都的电话号码是:010-87654321').group()) 运行代码,输出结果如下所示: 028-12345678 010-87654321
上面代码中在编译第一个正则表达式时使用 re.X 旗标,因此在表达式中可以换行,还可添加注释。将编译后的对象赋值给 a 变量,通过后面的调用语句的输出结果可以看到,该正则表达式完全没有问题。
3、 创建正则表达式
正则表达式用于匹配一批字符串,正则表达式同时也是一个特殊的字符串。在正则表达中有它可以支持的合法字符、特殊字符、预定义字符、方括号表达式、边界匹配符。这些都是有特殊用途的。
(1)、正则表达式所支持的合法字符如下表所示
字符 | 释义 |
---|---|
x | x表示任意合法的字符 |
\uhhhh | 十六进制值0xhhhh所表示的Unicode字符 |
\t | 制表符('\u0009') |
\n | 新行(换行)符('\u000A') |
\r | 回车符('\u000D') |
\f | 换页符('\u000C') |
\a | 报警(bell)符('\u0007') |
\e | Escape符('\u001B') |
\cx | x对应控制符,例如,\cM匹配Ctrl+M。x值必须是A~Z或a~z之一 |
(2)、正则表达式中的特殊字符
特殊字符有特殊用途,比如反斜线(\)是转义符。要匹配这些特殊字符,需要先将这些特殊字符转义,也就是在这些特殊字符前面添加一个反斜线(\)。正则表达式中的特殊字符如下所示:
特殊字符 | 说明 |
---|---|
$ | 匹配一行的结尾。要匹配$字符本身,就使用$(下同) |
^ | 匹配一行的开头 |
() | 标记子表达式(也就是组)的开始位置和结束位置 |
[] | 用于确定中括号表达式的开始位置和结束位置 |
{} | 用于标记前面子表达式的出现次数,如{m,n}匹配前一个字符m到n次 |
* | 指定前面的子表达式可以出现0次或多次 |
+ | 指定前面的子表达至少要出现1次,也可以出现多次 |
? | 指定前面的子表达式可以出现0次或1次 |
. | 匹配除换行符之外的任意单个字符 |
\ | 用于转义特殊字符,或者用于指定八进制、十六进制字符 |
| | 指定在两项之间任选一项 |
在正则表达中,通常都需要将多个字符拼接起来。示例如下:
>>> print(re.fullmatch(r'\u0041\\', 'A\\')) # 匹配到:A\ <_sre.SRE_Match object; span=(0, 2), match='A\\'> >>> print(re.fullmatch(r'\u0061\t', 'a\t')) # 匹配到:a<制表符> <_sre.SRE_Match object; span=(0, 2), match='a\t'> >>> print(re.search(r'\?\[', 'python?[isgood')) # 匹配到:?[ <_sre.SRE_Match object; span=(6, 8), match='?['>
有了上面的这些特殊字符,在正则表达式中还需要使用通配符,通配符是可以匹配多个字符的特殊字符。正则表达式中的通配符被称为“预定义字符”。
(3)、 正则表达式所支持的预定义字符
预定义字符有下面7个:
预定义字符 | 说明 |
---|---|
. | 默认可以匹配除换行符之外的任意字符。可使用re.S旗标来增加匹配换行符 |
\d | 匹配0~9的所有数字 |
\D | 匹配非数字 |
\s | 匹配所有的空白字符,包括空格、制表符、回车符、换页符、换行符等 |
\S | 匹配所有的非空白字符 |
\w | 匹配所有的单词字符,包括0~9的所有数字、26个英文字母和下划线(_)、汉字 |
\W | 匹配所有非单词字符 |
>>> re.findall(r'c\wt', 'cat, cbt, c8t, c_t') # 匹配结果如下所示 ['cat', 'cbt', 'c8t', 'c_t'] >>> re.findall(r'\d\d\d-\d\d\d-\d\d\d\d', '400-800-1234, 8001-5553-6666') # 匹配结果如下所示 ['400-800-1234']
有了上面这些预定义字符,还需要进一步使用方括号表达式来丰富正则表达式。
(4)、方括号表达式
方括号表达式 | 说明 |
---|---|
表示枚举 | 例如[abc],表示a、b、c其中任意一个字符 |
表示范围 | 例如[a-f],表示 a~f 范围内的任意字符;[\u0041-\u0056]表示十六进制字符\u0041到\u0056范围的字符。范围可以和枚举结合使用,如[a-cx-z]表示a~c、x~z范围内的任意字符 |
表示求否:^ | 例如abc表示非a、b、c的任意字符;a-f表示不是a~f范围的任意字符 |
方括号表达式使用灵活,几乎可以匹配任意字符。例如要匹配所有的中文字符,可以利用 [\\u0041-\\u0056] 的形式,因为所有的中文字符的 Unicode 值是连续的,只要找出所有中文字符中最小、最大的 Unicode 值,就可以利用这种形式来匹配出所有中文字符。
(5)、边界匹配符
Python 的正则表达式还有边界匹配符,可用于匹配行的开头、结尾,单词的边界等。边界匹配符如下所示:
边界匹配符 | 说明 |
---|---|
^ | 行的开头 |
$ | 行的结尾 |
\b | 单词的边界,即只能匹配单词前后的空白 |
\B | 非单词的边界,即只能匹配不在单词前后的空白 |
\A | 只匹配字符串的开头 |
\Z | 只匹配字符串的结尾,仅用于最后的结束符 |
4、 子表达式
正则表达式还可以使用圆括号表达式,可将多表达式组成一个子表达式,在圆括号中可以使用“或”运算符(|)。圆括号表达式也是功能丰富的用法之一。圆括号表达式,也叫子表达式(组)支持的用法如下:
(1)、(exp):匹配 exp 表达式并捕获成一个自动命名的组,后面可通过 “\1” 引用第一个捕获组所匹配的子串,通过 “\2” 引用第二个捕获组所匹配的子串,......,以此类推。示例如下:
>>> re.search(r'Windows (95|98|NT|2000)[\w ]+\1', 'Windows 98 publised in 98') <_sre.SRE_Match object; span=(0, 25), match='Windows 98 publised in 98'>
上面代码用到的正则表达式是 r'Windows (95|98|NT|2000)[\w ]+\1';紧接着的 [\w ]+ 表达式可匹配任意单词字符和空格,方括号后面的 “+” 表示方括号可出现1次或多次;最后是 “\1” 表示引用第一个组所匹配到的子串,假如第一个匹配到的是98,则 “\1”也必须是98,因此该正则表达式可匹配 “Windows 98 publised in 98”。如果将上面代码改为如下形式就不能匹配:
>>> print(re.search(r'Windows (95|98|NT|2000)[\w ]+\1', 'Windows 98 publised in 95')) None
从输出可看出,未能成功匹配到结果,是因为第一个组匹配到的子串是98,因此 “\1” 也必须引用子串98,所以该正则表达式不能匹配 “Windows 98 publised in 95”
(2)、(?P<name>exp):匹配 exp 表达式并捕获成命名组,该组的名字为 name。后面可通过 (?P=name) 来引用前面捕获的组。通过此处介绍可以看出,(exp) 和 (?P<name>exp) 的功能大致相同,只是 (exp) 捕获的组没有显式指定组名,因此后面只能使用\1、\2等方式来引用这种组所匹配的子串;而 (?P<name>exp) 捕获的组有名称,因此后面可通过 (?P=name) 的方式来引用命名组所匹配的子串。
(3)、(?P=name):引用 name 命名组所匹配的子串。
示例代码如下所示:
>>> re.search(r'<(?P<tag>\w+)>\w+</(?P=tag)>', '<div>hello</div>') <_sre.SRE_Match object; span=(0, 16), match='<div>hello</div>'>
上面代码中的正则表达式是 r'<(?P<tag>\w+)>\w+</(?P=tag)>',表达式开始是 “<” 符号,表示直接匹配该符号;接下来是一个命名组:(?P<tag>\w+),组名是 tag,该组可以匹配1个或多个任意字符;紧接着后面是 “>” 符号,这部分用于匹配一个 HTML 或 XML 标签。尖括号外面的 “\w+” 用于匹配标签中的内容;后面定义的 “</” 直接匹配这两个字符;之后的 (?P=tag) 就用于引用前面的 tag 组所匹配的子串,也就是说,该正则表达式要求内容必须在合理关闭的 HTML 或 XML 标签内。例如下面代码就不能匹配:
>>> print(re.search(r'<(?P<tag>\w+)>\w+</(?P=tag)>', '<div>hello</p>')) None
这是由于前后两个标签不相同,因此不能匹配。
(4)、(?:exp):匹配 exp 表达式并且不捕获。这种组与 (exp) 的区别就在于它是不捕获的,因此不能通过 \1、\2等方式来引用。例如下面这行代码运行时会报错:
re.search(r'Windows (?:95|98|NT|2000)[\w ]+\1', 'Windows 98 publised in 98')
将上面代码中的 \1 去掉后,表示不捕获前面匹配的组,就可以正常匹配:
>>> re.search(r'Windows (?:95|98|NT|2000)[\w ]+', 'Windows 98 publised in 98') <_sre.SRE_Match object; span=(0, 25), match='Windows 98 publised in 98'> # 如果不想匹配后面的数字,可将 [\w ]+ 部分修改为 [a-z ]+ >>> re.search(r'Windows (?:95|98|NT|2000)[a-z ]+', 'Windows 98 publised in 98') <_sre.SRE_Match object; span=(0, 23), match='Windows 98 publised in '>
(5)、(?<=exp):括号中的子模式必须出现在匹配内容的左侧,但 exp 不作为匹配结果的一部分。
(6)、(?=exp):括号中的子模式必须出现在匹配内容的右侧,但 exp 不作为匹配结果的一部分。
上面两个子表达式主要用于对匹配内容进行限定,括号中的子模式本身不作为匹配的一部分。例如要获取 HMTL 代码中的<a>标签的内容。
>>> re.search(r'(?<=<a>).+?(?=</a>)', 'hello! <a>michael.com</a>! technology') <_sre.SRE_Match object; span=(10, 21), match='michael.com'>
在上面的正则表达式中 (?<=<a>) 是一个限定组,该组的内容是 <a>,由于该组用了 (?<=exp) 声明,因此在被匹配内容的左侧必须有 <a>;后一个限定组是 (?=</a>),该组的内容是 </a>,该组用了 (?=exp) 声明,因此要求在被匹配内容的右侧必须出现 </a>。所以上面的正则表达式会将 <a> 和 </a> 之间的内容匹配出来。再例如:
>>> re.search(r'(?<=<a>).+?(?=</a>)', 'hello! <a><div>michael</div></a>! technology') <_sre.SRE_Match object; span=(10, 28), match='<div>michael</div>'>
(7)、 (?<!exp):括号中的子模式必须不出现在匹配内容的左侧,且 exp 不作为匹配的一部分。这个是 (?<=exp) 的逆向表达。
(8)、(?!exp):括号的子模式必须不出现在匹配内容的右侧,且 exp 不作为匹配的一部分。这个是 (?=exp) 的逆向表达。
(9)、(?#comment):注释组。“?#” 后的内容是注释,不影响正则表达式本身。示例如下:
>>> re.search(r'[a-zA-Z0-9_]{3,}(?#username)@michael\.com', 'hello stark@michael.com') <_sre.SRE_Match object; span=(6, 23), match='stark@michael.com'>
上面代码中的 (?#username) 就是注释,对正则表达式不会有什么影响,只用于对部分内容进行说明。
(10)、(?aiLmsux):旗标组,用于为整个正则表达式添加行内旗标,可同时指定一个或多个旗标。示例如下:
>>> re.findall(r'(?im)[a-z0-9]{3,}@michael\.com', 'Stark@MICHAEL.COM,\ ... Tom@michael.COM') ['Stark@MICHAEL.COM', 'Tom@michael.COM']
上面代码中的 (?im) 组表示该正则表达式在匹配时不区分大小写,并且可以匹配多行。所以匹配到多个结果,并以列表形式返回结果。如果将该旗标组去掉,就不能匹配到结果。
(11)、(?imsx-imsx:exp):只对当前组起作用的旗标。该组旗标与 (?aiLmsux) 组旗标的区别是,(?aiLmsux) 组旗标作用于整个正则表达式,而这组旗标只影响组内的子表达式。示例如下:
>>> re.search(r'(?i:[a-z0-9]){3,}@michael\.com', 'Stark@michael.com') <_sre.SRE_Match object; span=(0, 17), match='Stark@michael.com'>
上面这个表达中的 (?i:[a-z0-9]) 组表示子表达式不区分大小写,但整个表达式依然区分大小写。因此这个表达式可以匹配 Stark@michael.com,但不能匹配 Stark@Michael.com,因为后面部分依然要区分大小写。
如果在旗标前使用减号 “-”,则表明去掉该旗标。比如在执行 search() 方法时传入了 re.I 参数,这表示整个正则表达式不区分大小写;如果希望某个组内的表达式依然区分大小,则可使用 (-i:exp) 来表示。例如:
>>> re.search(r'(?-i:[a-z0-9]){3,}@michael\.com', 'stark@Michael.Com', re.I) <_sre.SRE_Match object; span=(0, 17), match='stark@Michael.Com'>
在 search() 方法中指定了 re.I 选项,表示整个正则表达式在匹配时不区分大小写;但是又要求用户名必须区分大小写,于是就把用户名部分放在用组定义成的子表达中,并为该子表达式指定 “?-i:” 选项(表明去除 re.I 选项),这样在组内的子表达式就会区分大小写。所在上面这个表达式可以匹配 stark@Michael.com、stark@michael.com,但不能匹配 Stark@Michael.com,因为用户名是区分大小写的。
5、贪婪模式与勉强模式
正则表达可以限定频度,用于限定前面的模式可以出现的次数。Python 正则表达式支持的频度限定有下面几种:
(1)、*:限定前面的子表达可以出现 0~N 次。例如 r'zo*' 能匹配 'z',也能匹配 'zoo'、'zooo'等。这里 * 等价于 {0,}。
(2)、+:限定前面的子表达可以出现 1~N 次。例如 r'zo+' 不能匹配 'z',可匹配 'zo'、'zoo'、'zooo'等。+ 等价于 {1,}。
(3)、?:限定前面的子表达出现 0~1 次。例如 r'zo?' 能匹配 'z' 和 'zo' 两个字符串。? 等价于 {0,1}。
(4)、{m,n}:m 和 n 均为大于 0 的整数,其中 m<=n,限定前面的子表达出现 m~n 次。例如 r'fo{1,3}d' 可匹配 'fod'、'food'、'foood'这三个字符串。
(5)、{n,}:n 是一个大于0的整数,限定前面的子表达式至少出现 n 次。例如 r'fo{2,}d' 可匹配 'food'、'foood'、'fooood'等字符串。
(6)、{,m}:m 是一个大于0的整数,限定前面的子表达最多出现 m 次。例如 r'fo{,3}d' 可匹配 'fd'、'fod'、'food'、'foood' 这四个字符串。
(7)、{n}:n 是一个大于0的整数,限定前面的子表达必须出现 n 次。例如 r'fo{2}d' 只能匹配 'food' 字符串。
在默认情况下,正则表达式的频度限定是贪婪模式的。贪婪模式指的是表达式中的模式会尽可能多的匹配字符。示例如下:
>>> re.search(r'@.+\.', 'stark@michael.com.cn') <_sre.SRE_Match object; span=(5, 18), match='@michael.com.'>
上面的正则表达式 r'@.+\.' 是匹配 @ 符号和点(.)号之间的全部内容,希望匹配的结果是 “@michael.”。但是由于在@和点号之间用的是 “.+” 表示匹配任意字符,而且此时是贪婪模式,因此 “.+” 会尽可多的进行匹配,只要最后有一个 “.” 结尾即可,所以匹配到的结果是 “@michael.com.”
只要在频度限定之后添加一个英文问号,贪婪模式就变成了勉强模式,勉强模式指的是表达式中的模式会尽可能少的匹配字符。示例如下:
>>> re.search(r'@.+?\.', 'stark@michael.com.cn.') <_sre.SRE_Match object; span=(5, 14), match='@michael.'>
这次将正则表达式中间部分由 “.+” 改为 “.+?” 就成了勉强模式。该模式会尽可能少的匹配字符,只要它最后有一个“.”结尾即可,因此匹配结果是 '@michael.'。