正则表达式是处理字符串强大的工具,拥有独特的语法和独立的处理引擎,效率可能不如str自带的方法,但功能十分强大。我们将学习正则表达式的基本使用。
认识正则表达式
正则表达式是一个特殊字符序列,能帮助用户检查一个字符串是否与某种模式匹配,从而达成快速检索或替换符合某个模式、规则的文本。例如,可以在文档中使用一个正则表达式表示特定文字,然后将其全部删除或替换成别的文字。
Python自1.5版本起增加了re模块,它提供了Perl风格的正则表达式模式,re模块使Python语言拥有全部的正则表达式功能。compile函数根据一个模式字符串和可选的标志参数生成一个正则表达式对象,该对象拥有一系列方法用于正则表达式匹配和替换。
re模块提供与compile的函数功能完全一致的函数,这些函数使用模式字符串作为第一个参数。
字符串是编程时涉及最多的数据结构,对字符串操作的需求几乎无处不在。
在开始后续介绍前,我们先看表1和表2。表1展示了一些特殊字符在正则表达式中的独特应用,表2展示某些字符类在正则表达式中的应用。
通过表1和表2可以看到,一些特殊字符虽然很简短,但功能非常强大。下面介绍一些更详尽的正则表达式的使用方式。
例如,我们要判断一个字符串是否是合法的Email地址,可以用编程的方式提取@前后的子串,再分别判断是否是单词和域名。不过这样做不但需要写一堆麻烦的代码,而且写出来的代码难以重复使用,面对不同的需求可能需要使用不同的代码实现。
正则表达式是匹配字符串的强有力的武器。正则表达式的设计思想是用描述性语言为字符串定义一个规则,凡是符合规则的字符串,我们就认为“匹配”,否则就不匹配。正则表达式的大致匹配过程是:依次拿出表达式和文本中的字符比较,如果每一个字符都能匹配,匹配就成功;一旦有匹配不成功的字符,匹配就失败。
用正则表达式判断一个字符串是否是合法的Email的方法是:
(1)创建一个匹配Email的正则表达式。
(2)用该正则表达式匹配用户的输入从而判断是否合法。
下面我们介绍如何使用正则表达式描述字符。
在正则表达式中,如果直接给出字符,就是精确匹配。从表11-1可知,用\d可以匹配一个数字,用\w可以匹配一个字母或数字,例如:
- '00\d'可以匹配'007',但无法匹配'00q'。
- '\d\d\d'可以匹配'123'。
- \w\w\d'可以匹配'py3'。
- .可以匹配任意字符,所以'py.'可以匹配'pyc' 'pyo' 'py!'等。
在正则表达式中,要匹配变长的字符,用*表示任意个数的字符(包括0个),用+表示至少一个字符,用?表示0个或1个字符,用{n}表示n个字符,用{n,m}表示n~m个字符。
下面我们看一个更复杂的例子:\d{3}\s+\d{3,8}。该字符串从左到右解读如下:
\d{3}表示匹配3个数字,如'010';\s可以匹配一个空格(包括Tab等空白符),所以\s+表示至少有一个空格,如匹配' '、''等;\d{3,8}表示3~8个数字,如'1234567'。
综上所述,正则表达式可以匹配以任意个数的空格隔开的带区号的电话号码。
如果要匹配'010-12345'这样的号码呢?由于'-'是特殊字符,在正则表达式中要用''转义,因此用正则表达式表示为\d{3}-\d{3,8}。
我们前面讨论了正则表达式的基本使用方法,不过如果需要匹配带有字符串的字符串(如'010 - 12345'),使用前面的方式就做不到了,在此我们继续讨论一些更复杂的匹配方式。
要更精确地匹配,可以用[]表示范围,例如:
- [0-9a-zA-Z_]用以匹配数字、字母或下划线,这种方式可以在一些场所做输入值或命名的合法性校验。
- [0-9a-zA-Z_]+可以匹配至少由一个数字、字母或下划线组成的字符串,如'a100' '0_Z' 'Py3000'。这种方式可以校验一个字符串是否包含数字、字母或下划线。
- [a-zA-Z_][0-9a-zA-Z_]*可以匹配由字母或下划线开头,后接任意个数字、字母或下划线组成的字符串,也就是Python的合法变量。
- [a-zA-Z_][0-9a-zA-Z_]{0, 19}更精确地限制了变量的长度是1~20个字符(前面1个字符+后面最多19个字符)。
- A|B用于匹配A或B,如(P|p)ython可以匹配'Python'或'python'。
- 表示行的开头,\d表示必须以数字开头。
- 表示必须以数字结束。
这里提供了正则表达式更高级的使用,正则表达式更多匹配模式可以百度。
re模块
经过前面的知识储备,我们可以在Python中使用正则表达式了。Python通过re模块提供对正则表达式的支持。
1 re.match
函数
一般使用re的步骤是先将正则表达式的字符串形式编译为Pattern实例,然后使用Pattern实例处理文本并获得匹配结果(一个match函数),最后使用match函数获得信息,进行其他操作。
re.match函数尝试从字符串的起始位置匹配一个模式,该函数语法如下:
re.match(pattern, string, flags=0)
函数参数说明:pattern指匹配的正则表达式;string指要匹配的字符串;flags为标志位,用于控制正则表达式的匹配方式,如是否区分大小写、多行匹配等。
如果匹配成功,re.match方法就返回一个匹配的对象,否则返回None。
例如:
#! /usr/bin/python3
# -*- coding:UTF-8 -*-
import re
print(re.match('hello', 'hello world').span()) # 在起始位置匹配
print(re.match('world', 'hello world')) # 不在起始位置匹配
执行结果如下:
(0, 5)
None
2 re.search
方法
在re模块中,除了match函数外,search方法也经常使用。
re.search方法用于扫描整个字符串并返回第一个成功匹配的字符,语法如下:
re.search(pattern, string, flags=0)
函数参数说明:pattern指匹配的正则表达式;string指要匹配的字符串;flags为标志位,用于控制正则表达式的匹配方式,如是否区分大小写、多行匹配等。
如果匹配成功,re.search方法就返回一个匹配的对象,否则返回None。
例如:
#! /usr/bin/python3
# -*- coding:UTF-8 -*-
import re
print(re.search('hello', 'hello world').span()) # 在起始位置匹配
print(re.search('world', 'hello world').span()) # 不在起始位置匹配
执行结果如下:
(0, 5)
(6, 11)
3 re.match
与re.search
的区别
re.match函数只匹配字符串开始的字符,如果开始的字符不符合正则表达式,匹配就会失败,函数返回None。
re.search方法匹配整个字符串,直到找到一个匹配的对象,匹配结束没找到匹配值才返回None。
例如:
#! /usr/bin/python3
# -*- coding:UTF-8 -*-
import re
line = 'Cats are smarter than dogs'
matchObj = re.match(r'dogs', line, re.M | re.I)
if matchObj:
print('use match,the match string is: ', matchObj.group())
else:
print("No match string!!")
matchObj = re.search( r'dogs', line, re.M | re.I)
if matchObj:
print('use search,the match string is: ', matchObj.group())
else:
print("No match string!!")
执行结果如下:
No match string!!
use search,the match string is: dogs
该示例使用了match类中的分组方法——group方法。该方法定义如下:
def group(self, *args):
"""Return one or more subgroups of the match.
:rtype: T | tuple
"""
pass
group([group1, …]):获得一个或多个分组截获的字符串,指定多个参数时以元组的形式返回。group1可以使用编号,也可以使用别名。编号0代表整个匹配的子串。不填写参数时,返回group(0);没有截获字符串的组时,返回None;截获多次字符串的组时,返回最后一次截获的子串。
还有一个常用的分组方法groups。
groups([default]):以元组形式返回全部分组截获的字符串,相当于调用group(1,2,…last)。default表示没有截获字符串的组以这个值代替,默认为None。
贪婪模式和非贪婪模式
正则表达式通常使用于查找匹配的字符串。Python里数量词默认是贪婪的(在少数语言里也可能默认非贪婪),总是尝试匹配尽可能多的字符;非贪婪模式正好相反,总是尝试匹配尽可能少的字符。
例如,正则表达式"ab"如果用于查找"abbbc",就会找到"abbb"。如果使用非贪婪的数量词"ab?",就会找到"a"。
例如:
#! /usr/bin/python3
# -*- coding:UTF-8 -*-
print(re.match(r'^(\d+)(0*)$', '102300').groups())
执行结果为:
('102300', '')
由于\d+采用贪婪匹配,直接把后面的0全部匹配了,结果0只能匹配空字符串。要让0能够匹配到后面的两个0,必须让\d+采用非贪婪匹配(尽可能少匹配)。在0*后面加一个?就可以让\d+采用非贪婪匹配。具体实现如下:
#! /usr/bin/python3
# -*- coding:UTF-8 -*-
print(re.match(r'^(\d+?)(0*)$', '102300').groups())
执行结果为:
('1023', '00')
替换
Python的re模块提供了re.sub,用于替换字符串中的匹配项。
sub(repl, string[, count]) | re.sub(pattern, repl, string[, count]):使用repl替换string中每一个匹配的子串后返回替换后的字符串。当repl是一个方法时,这个方法应当只接收一个参数(match对象),并返回一个字符串用于替换(返回的字符串中不能再引用分组)。count用于指定最多替换次数,不指定时全部替换。
例如:
#! /usr/bin/python3
# -*- coding:UTF-8 -*-
pt = re.compile(r'(w+) (w+)')
greeting = 'i say, hello world!'
print(pt.sub(r'2 1', greeting))
def func(m):
return m.group(1).title()+' '+m.group(2).title()
print(pt.sub(func, greeting))
执行结果为:
i say, hello world!
i say, hello world!
编译
当我们在Python中使用正则表达式时,re模块内部会做两件事情:
(1)编译正则表达式,如果正则表达式的字符串本身不合法,就会报错。
(2)用编译后的正则表达式匹配字符串。
如果一个正则表达式需要重复使用几千次,出于效率的考虑,我们可以预编译该正则表达式,这样重复使用时就不需要编译这个步骤了,直接匹配即可,例如:
#! /usr/bin/python3
# -*- coding:UTF-8 -*-
re_telephone = re.compile(r'^(\d{3})-(\d{3,8})$')
print(re_telephone.match('010-12345').groups())
print(re_telephone.match('010-8086').groups())
执行结果为:
('010', '12345')
('010', '8086')
网友评论