正则表达式
在Python开发中经常会遇到参数格式的校验,然平时对re表达式了解的不够深入,今天做一个完整的表达式学习记录笔记
Python re库
正则表达式基本语法 正则表达式就是使用预先定义好的特定字符以及这些特定字符的组成字符串的匹配表达式,然后利用表达式去匹配或提取字符串。因此,我们首先需要熟悉正则表达式中的特定字符以及它们的含义、用法。下面将正则表达式中的特殊字符分类介绍
基本字符介绍 (1). 点号,表示匹配除换行"\n"外的任意1个字符。假设表达式:a.c,则匹配abc/a1c,不匹配ac。但是在python的re模块函数可以通过设置re.S标志让它也匹配换行符。
import re
re.findall(r'a.c','abc')
['abc']
re.findall(r'a.c','ac')
[]
re.findall(r'a.c','a\nc',re.S)
['a\nc']
(2)\ 转义字符,使后一个字符改变原来的含义。假设表达式为a.c,则仅匹配a.c,不匹配abc、a1c等a与c之间非点号"."的字符串。在转义符""之后点号".失去了原来代表任意字符的含义。
re.findall(r'a.c','a.c')
['a.c']
re.findall(r'a.c','abc')
[]
字符集
[...]字符集,对应的位置可以是字符集中任意字符,字符集中的字符可以逐个列出,也可以给出范围,或者如果第一个字符是^表示取反。
场景1:
[...]字符集中的字符逐个列出。如[bcd]。假设正则表达式为a[bcd]e,则匹配abe、ade,但不匹配afe。实例如下:
re.findall(r'a[bcd]e', 'abe')
['abe']
re.findall(r'a[bcd]e', 'afe')
[]
场景2:
[...]字符集中的字符一范围给出。假设表达式为a[a-d]e, 相当于a[abcd]e.则匹配abe/ade/ace
re.findall(r'a[a-d]e', 'abe')
['abe']
re.findall(r'a[a-d]e', 'ace')
['ace']
re.findall(r'a[a-d]e', 'ade')
['ade']
场景3:
如果[...]字符集第一个字符是^ 则表示取反。假设表达式为a[^abc]e,则匹配a到c之间的,包含一个字符的 不是 a b c 这三个字符其中任意一个的
re.findadll(r'a[^abc]e', 'ace')
[]
re.findadll(r'a[^abc]e', 'afe')
['afe']
那如果^ 字符不是在第一个字符,它就表示一个普通的字符。假设表达式为a[a^bc]e,则匹配abe、a^e、ace,不匹配afe。
>>> re.findall(r'a[a^bc]e','abe')
['abe']
>>> re.findall(r'a[a^bc]e','a^e')
['a^e']
>>> re.findall(r'a[a^bc]e','afe')
[]
场景4:
特殊字符在字符集[...]中都失去其原有的特殊含义。假设表达式为a[a.bc]e,则匹配abe、ace、a.e,不匹配afe
>>> re.findall(r'a[a.bc]e','a.e') #特殊符号"."在中括号内失去了原有的含义。
['a.e']
>>> re.findall(r'a[a.bc]e','afe')
[]
预定义字符集
\d 表示一个数字[0-9]
\D 表示一个非数字[^0-9]
\s 表示一个空白字符[<空格>\t\r\n\f\v]
\S 表示一个飞空字符[^\s]
\w 表示一个单词字符 [a-zA-Z0-9_]
\W 表示一个非单词字符[^\w]
1)\d 表示1个数字,相当于[0-9]。假设表达式为a\dc,则匹配a1c,不匹配abc
re.findaall(r'a\dc', 'a1c')
['a1c']
re.findall(r'a\dc','abc')
[]
(2)\D 表示1个非数字,相当于[^0-9]。假设表达式a\Dc,则匹配abc,不匹配a1c
re.findall(r'a\dc','a1c')
['a1c']
re.findall(r'a\dc','abc')
[]
3)\s 表示1个空白字符,相当于[<空格>\t\r\n\f\v]。假设表达式为a\sc,则匹配a c,不匹配abc
['a c']
4)\S 表示1个非空白字符,相当于[^\s]。假设表达式为a\Sc,则匹配abc,不匹配a c
re.findall(r'a\Sc','abc')
['abc']
5)\w 表示1个单词字符,相当于[a-zA-z0-9_]。假设表达式为a\wc,则匹配abc,不匹配a c
re.findall(r'a\wc', 'abc')
['abc']
re.findall(r'a\wc', 'a1c')
['a1c']
re.findall(r'a\wc', 'a\tc')
[]
6)\W 表示1个非单词字符数字,相当于[^\w]。假设表达式为a\Wc,则匹配a c,不匹配abc
re.findall(r'a\Wc', 'a1c')
[]
re.findall(r'a\Wc', 'a.c')
['a.c']
数量词
* 表示匹配前一个字符0~n次
+ 表示匹配前一个字符1~n次
? 表示匹配钱一个字符0或1次
{m} 表示匹配前一个字符m次
{m,n} 表示匹配前一个字符m到n次
1)* 表示匹配前一个字符0~n次。假设表达式为abc*,则匹配ab、abc、abcc;
re.findall(r'abc*','abcabccabc')
['abc', 'abcc', 'abc']
re.findall(r'abc*','ab')
['ab']
2)+ 表示匹配前一个字符1~n次。假设表达式为abc+,则匹配abc、abccc,不匹配ab
re.findall(r'abc+', 'ab')
[]
re.findall(r'abc+','abcc')
['abcc']
3)? 表示匹配前一个字符0或1次。假设表达式为abc?,则匹配ab、abc
re.findall(r'abc?','ab')
['ab']
re.findall(r'abc?','abcabccabc')
['abc', 'abcc', 'abc']
4){m} 表示匹配前一个字符m次。假设表达式为abc{2},则匹配abcc,不匹配abc
re.findall(r'ab{2}', 'abbcc')
['abb']
re.findall(r'abc{2}', 'abc')
[]
re.findall(r'[0-9]{2}', '223')
['22']
re.findall(r'[0-9]{2}', '2234')
['22', '34']
re.findall(r'[0-9]{2}', '2')
[]
5){m,n} 表示匹配前一个字符m到n次。假设表达式为abc{2,3},则匹配abcc、abccc,不匹配abc
re.findall(r'abc{2,3}','abcc')
['abcc']
re.findall(r'abc{2,3}','abc')
[]
正则表达式默认匹配方式是贪婪匹配,也就是最长匹配。所以会出现上述的匹配结果。那如果是想最短匹配呢?只需要在数量限定符后面加一个问号"?"就可以了
re.findall(r'abc*','abcc') #最长匹配
['abcc']
re.findall(r'abc*?','abcc') #最短匹配
['ab']>>> re.findall(r'abc??','abc')
['ab']
>>> re.findall(r'abc{2,3}','abccc')
['abccc']
>>> re.findall(r'abc{2,3}?','abccc')
['abcc']
边界匹配
^ 表示从字符串起始位置开始匹配
$ 表示从字符串结尾开始匹配
\A 表示从 字符串起始位置开始匹配
\Z 表示从字符串结束部分开始匹配
\b 表示匹配一个单词边界
1)^ 表示从字符串起始位置开始匹配。假设表达式为^abc,则匹配abcd,不匹配babc
re.findall(r'^abc','abcd')
['abc']
re.findall(r'^abc','babc')
[]
2),则匹配ccabc,不匹配abcd
re.findall(r'abc$','ccabc')
['abc']
re.findall(r'abc$','ccabcd')
[]
3)\A表示从 字符串起始位置开始匹配。 假设表达式为\Aabc,则匹配abcd,不匹配babc
re.findall(r'\Aabc','abcd')
['abc']
re.findall(r'\Aabc','babc')
[]
4)\Z 表示从字符串结束部分开始匹配。如果是存在换行,只匹配到换行前的结束字符串 假设表达式为abc\Z,则匹配abc,不匹配abcd
re.findall(r'abc\Z','abc')
['abc']
re.findall(r'abc\Z','abcd')
[]
5)\b 表示匹配一个单词边界。假设表达式为'er\b' 则匹配"never" 中的 'er',但不能匹配 "verb" 中的 'er'
re.findall(r'er\b','never')
['er']
re.findall(r':\b','never:')
[]
re.findall(r':\b','nev:er')
[':']
6)\B 表示匹配非单词边界。假设表达式为'er\B'则可以匹配 "verb" 中的 'er',但不能匹配 "never" 中的 'er'。
re.findall(r'er\B','verb')
['er']
逻辑分组
| 表示匹配|左右表达式的任意一个
(...) 作为分组,每遇到一个(,分组编号加1,使用分组的好处是匹配的子串会保存到一个子组
(?P<name>...) 分组除原有编号外,再加一个别名
1)| 表示匹配|左右表达式的任意一个。假设表达式为abc|def,则匹配abc、def
re.findall(r'abc|def','abcdef')
['abc', 'def']
2)(...) 作为分组,每遇到一个(,分组编号加1,使用分组的好处是匹配的子串会保存到一个子组,便于以后使用。假设表达式为(\d{4})-(\d{2})-(\d{2}),则用于匹配2017-06-03,然后用分组编号1、2、3分别获取年、月、日三个值。。说明:如果这里match()函数和match对象的group()函数理解有困难,可以先跳过,后面再返回查看。
mat = re.search(r'(\d{4})-(\d{2})-(\d){2}', '2017-06-03 00:09')
mat.group()
'2017-06-03'
mat.group(1)
'2017'
...
3)(?P...) 分组除原有编号外,再加一个别名 。假设表达式为(?P\d{4})-(?P\d{2})-(?P\d{2}),则匹配用于匹配2017-06-03,然后用命名分组名称Year、Month、Day获取年、月、日3个值。
mat = re.search(r'(?P<Year>\d{4})-(?P<Month>\d{2})-(?P<Day>\d{2})','2017-06-03')
mat.group('Day')
'03'
4)<number> 引用编号为number的分组匹配到的字符串。假设表达式为 (abc)ee\1,则匹配abceeabc,不匹配abceeabd。
re模块常用函数
match 从头部开始匹配字符串,只返回一次匹配成功的对象,否则是None
search 在string中匹配字符串,只返回第1次匹配成功的对象,否则返回None。
split 根据pattern分隔string,maxsplit表示最大分个数。
compile 编译正则表达式pattern,返回一个pattern对象。
sub 根据指定的正则表达式,替换字符串中的字串。
subn 作用和sub()相同,返回一个二元元组。
findall 根据pattern在string中匹配字符串。如果匹配成功,返回包含匹配结果的列表,否则,返回空列表
1、match(pattern,string,flags=0)
根据pattern从string的头部开始匹配字符串,只返回第1次匹配成功的对象,否则,返回None。flags表示规则选项。
import re
re.match(r'(\d{4})-(\d){2}-(\d){2}\s(\d){2}:(\d){2}:(\d){2}','2020-09-09 17:45:00')
<_sre.SRE_Match object at 0x2d87350>
re.match(r'(\d{4})-(\d){2}-(\d){2}\s(\d){2}:(\d){2}:(\d){2}','2020-09-09 17:45')
2、search(pattern,string,flags=0)
根据pattern在string中匹配字符串,只返回第1次匹配成功的对象,否则,返回None。
import re
Str='Python:Java:C'
re.search(r'Python',Str) #匹配成功
<_sre.SRE_Match object at 0x00000000060D7D98>
3、split(pattern,string,maxsplit=0)
根据pattern分隔string,maxsplit表示最大分个数。
import re
Str='Python:Java:C'
re.split(r':',Str)
['Python', 'Java', 'C']
Str = "Python:Java:Shell|C++|Ruby"
re.split(r'[:|]',Str) #指定多种分隔符
['Python', 'Java', 'Shell', 'C++', 'Ruby']
4、compile(pattern,flags=0)
正则表达式pattern,返回一个pattern对象
import re
regex = r'Python'
Str='Python:Java:C'
p = re.compile(regex)
p.match(Str)
<_sre.SRE_Match object at 0x00000000060D7D98>
说明:pattern对象方法除了match(),还包括search()、findall()、finditer()。
5、sub(pattern,repl,string,count=0)
根据指定的正则表达式,替换字符串中的子串。pattern是一个正则表达式,repl是用于替换的字符串,string是源字符串,如果count为0,则返回string中匹配的所有结果。如果count>0,则返回前count个匹配结果。原字符串不变
import re
Str='Python:Java:C'
re.sub(r'P.*n','Ruby',Str)
'Ruby:Java:C'
6、subn(pattern,repl,string,count=0)
作用和sub()相同,返回一个二元元组。第一个元素是替换结果,第2个元素是替换的次数。
import re
Str='Python:Java:C'
re.subn(r'P.*:','Ruby:',Str) #返回替换次数
('Ruby:C', 1)
re.subn(r'P.*?:','Ruby:',Str) #注意匹配中多了个?号,替换内容不同了
('Ruby:Java:C', 1)
7、findall(pattern,string,flags=0)
根据pattern在string中匹配字符串。如果匹配成功,返回包含匹配结果的列表,否则,返回空列表。但pattern中有分组时,返回包含多个元组的列表,每个元组对应一个分组。
import re
regex = r'\w+' #\w表示匹配包括下划线的任何单词字符
Str='Python:Java:C'
p = re.compile(regex)
p.findall(Str)
['Python', 'Java', 'C']
描述完re模块的主要函数后,这里再强调 函数中的flags参数 和 re.compile() 函数。
1、re.flags参数
通过查看re模块函数的原型可以发现,函数参数几乎都有flags参数,该参数用于设置匹配的附加选项。例如,是否忽略大小写、是否支持多行匹配等。常用的re模块规则选项如下所示:
I或IGNORECASE 忽略大小写
L或LOCALE 字符集本地化,用于多语言环境
M或MULTILINE 多行匹配
S或DOTALL 使.匹配包括\n在内的所有字符
X或VERBOSE 忽略正则表达式中的空白、换行,方便添加注释
U或UNICODE \w、\W、\b、\B、\d、\D、\s和\S都将使用Unicode
通过一个忽略大小写的实例看下用法:
import re
Str='Python:Java:C'
re.match(r'python',Str) #匹配失败
re.match(r'python',Str,re.I) #加re.I,匹配成功
<_sre.SRE_Match object at 0x00000000060D7D98>
2、re.compile() 函数
正则表达式的解析非常耗时,如果多次使用findall()的方式匹配字符串搜索效率可能比较低。如果多次使用同一规则匹配字符串,可以使用compile()进行预编译,compile函数返回1个pattern对象。该对象拥有一系列方法用于查找、替换或扩展字符串,从而提供字符串的匹配速度。patter对象的属性和方法如下
pattern #获取当前使用的正则表达式
match(string,flags=0) #同re.match()
search(string,flags=0) #同re.searc()
findall(string,flags=0) #查找所有符合pattern对象匹配条件的结果,返回1个包含匹配结果的列表。
finditer(string,flags=0) #返回一个包含匹配结果的地址
另外,函数compile()通常与match()、search()、group()一起使用对含有分组的正则表达式进行解析。正则表达式的分组从左往右开始计数,第1个出现的圆括号标记为第1组,依次类推。此外还有0号组,0号组用于存储匹配整个正则表达式的结果。match()和search()将返回一个match对象,match对象提供了一系列的方法和属性来管理匹配的结果。match对象的方法和属性如下:
group(index=0) #某个分组的匹配结果。默认匹配整个正则表达式
groups() #所有分组的匹配结果,每个分组的结果组成1个列表返回
举例,匹配身份证号码并获取身份证中的年、月、日信息。
regex = r'[1-9][0-9]{5}(\d{4})(\d{2})(\d{2})[0-9]{3}[0-9X]'
Str = '11010019950807532X'
p = re.compile(regex)
m = p.match(Str)
m.groups()
('1995', '08', '07')
m.group(1)
'1995'
m.group(2)
'08'
m.group(3)
'07'
应用实例
阅读文档,练习 匹配日期格式
'2020-09-09 17:45:00'
^[0-9]{4}\S{1}[0-9]{2}\S[0-9]{2}\s[0-9]{2}\S[0-9]{2}\S[0-9]{2}\S
^[0-9]{4}-\b[0-9]{2}-\b0-9]{2}-\b\s[0-9]{2}:\b[0-9]{2}:\b[0-9]{2}$
([0-9]{4})-([0-9]){2}-([0-9]){2}\s([0-9]){2}:([0-9]){2}:([0-9]){2}$
(\d{4})-(\d){2}-(\d){2}\s(\d){2}:(\d){2}:(\d){2}
(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})\s(?P<hour>\d{2}):(?P<minute>\d{2}):(?P<second>\d{2})
案例 将时间格式为'yyy-MM-dd hh:mm'转为'yyy-MM-dd hh:mm:ss'的用00补齐
import re
p = r'(\d{4})-(\d){2}-(\d){2}\s(\d){2}:(\d){2}$'
str = u'2020-09-09 17:45'
if re.match(p, str):
str = str + ':00'