一、概述
- 字符串是编程时用到的最多的一种数据类型,在工作中需要对字符串进行操作的场景无处不在,例如:
判断一个字符串是否是合法的Email地址
上面的需求虽然可以通过提取@字符、分别判断@前后的单词和域名来达到目的,但是这种做不仅繁琐,而且代码难以复用
正则表达式的设计思想是使用一种描述性的语言来给字符串定义一个规则,凡是符合规则的字符串,就会被认定为匹配,反之就是不匹配。而上述情况就可以使用正则表达式来进行匹配字符串,从而达到目的,并且可以进行复用。
使用正则表达式对上述需求进行操作的步骤是:
创建一个匹配Email的正则表达式
使用正则表达式匹配输入的字符串是否匹配
二、正则表达式描述符
- 正则表达式同样也是使用字符串表示的,下面来了解如何使用正则表达式来描述字符:
==(1)==在正则表达式中,如果直接给出字符,就是精确匹配,使用\d可以匹配一个数字,使用\w可以匹配一个字母或者数字,例如:
00\d:可以匹配以00开头,以数字结尾,比如007、004等,但是无法匹配00A、00v这种的
\d\d\d:可以匹配三个任意数字,比如123、543等
\w\w\d:可以匹配前两位以字母或数字开头,以数字结尾,比如a33、cc3、4f1等
==(2)==使用.可以匹配任意字符,例如:
py.:可以匹配以py开头,以任意字符结尾,比如py4、pya、py-等
==(3)==想要匹配变长的字符,可以使用*表示匹配任意字符,包括0个字符,使用+表示至少1个字符,使用?表示0或1个字符,使用{n,m}表示n到m个字符,下面来看案例:
\d{3}\s+\d{3,8}:从左到右分析,\d{3}匹配3个数字,比如123这样,\s可以匹配一个空格,包括TAB等空白符,而\s+就表示匹配至少一个空格,例如' '、' '等,\d{3,8}表示匹配3到8个数字,表示数字的数量最少是3,最大是8,比如123456、123、12345678这样。通过这个正则表达式,可以匹配带区号的电话号码
==(4)==想要更加精确的匹配字符,可以使用[]表示范围:
[0-9a-zA-Z\_]:可以匹配一个数字、字母或者一个下划线
[0-9a-z-A-Z\_]+:可以匹配至少由1个数字、字母或下划线组成的字符串,匹配数量大于1
[a-zA-Z\_][0-9a-zA-Z\_]*:可以匹配由一个字母或下划线开头,然后接任意数量的由数字、字母、下划线组成的字符串
[a-zA-Z\_][0-9a-zA-Z\_]{0,19}:和上面的匹配规则相同,只是限制了字符的长度
==(5)==还有其他的特殊字符例如:
|:表示或的意思,比如A|B,可以匹配A或者B
^:表示开头,比如^A,就是匹配以A开头的
$:表示末尾,比如A$,就是匹配以A结尾的
注意:
如果不使用特殊字符,只是单纯的字符串的话,那么相当于是包含的意思,例如,正则表达式为py,而字符串第一行为python第二行为py,使用正则表达式会全部进行匹配,即两行字符串都会进行匹配,如果正则表达式为^py$,那么正则表达式就只会匹配第二行的py了
三、re模块
- 通过上面的了解,我们就可以在Python中使用正则表达式了,Python提供了
re
模块,其中包含了所有正则表达式的功能 - Python中有些字符串本身也是通过
\
转义的,而使用r
前缀就无需考虑转义问题,例如:
- 通过 \ 进行转义 >>> s = 'ABC\\-001' >>> print(s) ABC\-001 - 使用 r 进行转义 >>> s = r'ABC\-001' >>> print(s) ABC\-001
- 下面来看如何判断正则表达式是否匹配:
使用
match()
方法判断是否匹配,如果匹配成功会返回一个Match
对象,反之返回None
match()
:第一个参数是正则表达式,第二个参数是要匹配的字符串
>>> import re >>> re.match(r'^\d{3}\-\d{3,8}$','010-12345') <re.Match object; span=(0, 9), match='010-12345'> #匹配成功返回match对象 >>> re.match(r'^\d{3}\-\d{3,8}$','010 12345') >>> a = re.match(r'^\d{3}\-\d{3,8}$','010 12345') >>> print(a) None #匹配不成功,返回None
- 常见正则表达式判断方法:
test = '字符串' if re.match(r'正则表达式',test): print('ok') else: print('failed')
四、切分字符串
- 使用正则表达式切分字符串比使用固定的字符更加灵活,下面是正常的切分代码:
>>> 'ab c'.split(' ') ['ab', '', '', '', 'c'] #可以看到无法识别连续的空格
使用正则表达式:
>>> re.split(r'\s+','ab c') ['ab', 'c'] #可以看到相同的字符串可以正常分割 >>> re.split(r'[\s\,]+','a,b, c d') #加入 , 进行分割 ['a', 'b', 'c', 'd'] >>> re.split(r'[\s\,\:]+','a,b:::, c d') #加入 : 进行分割 ['a', 'b', 'c', 'd']
五、分组
- 除了简单的判断是否匹配之外,正则表达式还有提取
子串
的强大功能,使用()
表示的就是要提取的分组Group,例如:
^(\d{3})-(\d{3,8})$:这个正则表达式定义了两个分组,可以直接从匹配的字符串中提取出区号和本地号码
>>> m = re.match(r'^(\d{3})-(\d{3,8})$','010-12345') >>> m <re.Match object; span=(0, 9), match='010-12345'> - 如果在正则表达式中定义了组,就可以在match对象上使用group()方法提取出子串来,例如: >>> m.group(0) #group(0)永远是整个匹配的字符串 '010-12345' >>> m.group(1) #后续的1、2、3...就代表着第1、2、3...个子串 '010' >>> m.group(2)
下面来看一个关于分组的案例
>>> t = '22:33:44' >>> m = re.match(r'^(0[0-9]|1[0-9]|2[0-3]|[0-9])\:(0[0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9]|[0-9])\:(0[0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9]|[0-9])$', t) >>> m.group(0) '22:33:44' >>> m.group(1) '22' >>> m.group(2) '33' >>> m.group(3) '44'
- 上述正则虽然可以匹配正常的日期格式,但是对
2-30
、4-31
这样的非法日期还是无能为力,或者是写出来非常困难,这时就需要程序配合识别了
六、贪婪匹配
- 正则表达式默认是贪婪匹配,也就是匹配尽可能多的字符串,例如:
- 匹配出数字后面的0 >>> re.match(r'^(\d+)(0*)$','102300').groups() ('102300', '')
- 由于
\d+
组采用贪婪匹配,直接把后面的0
全部匹配了,结果0*
组只能匹配空字符串了 - 想让
\d+
采用非贪婪匹配,可以加个?
即可:
>>> re.match(r'^(\d+?)(0*)$','102300').groups() ('1023', '00') - 可以看到加了 ? 后,0* 组也匹配了字符串
七、编译
- 在使用正则表达式时,Python的re模块内部会做两件事:
- 编译正则表达式,如果正则表达式的字符串本身语法不正确,那么就会报错
- 使用编译后的正则表达式去匹配字符串
可以看到每次使用正则表达式时都会经过编译的过程,如果一个正则表达式要使用上千次,每次都进行编译的效率显然是有点多余,所以处于效率的考虑,我们可以预编译使用的正则表达式,之后就无需编译这个步骤了,例如
>>> import re >>> re_telephone = re.compile(r'^(\d{3})-(\d{3,8})$') >>> re_telephone.match('010-12345').groups() ('010', '12345') >>> re_telephone.match('010-80808').groups() ('010', '80808')
- 可以看到只需一次编译,后续直接就可以套用正则进行字符串匹配
八、案例
- 匹配email:
# -*- coding: utf-8 -*- from http.client import FAILED_DEPENDENCY import re def is_valid_email(addr): if re.match(r'^(\w+(\.)?\w+)@(\w+)(.com){1}$', addr): print("ok") return True else: print("faild") return False # 测试: assert is_valid_email('someone@gmail.com') assert is_valid_email('bill.gates@microsoft.com') assert not is_valid_email('bob#example.com') assert not is_valid_email('mr-bob@example.com') print('ok') - 执行 ok ok faild faild ok
取出带名字的email地址
# -*- coding: utf-8 -*- from http.client import FAILED_DEPENDENCY import re def name_of_email(addr): res = re.match(r'^<?(\w+\s?\w+)>?\s?(\w){0,}@(\w+\.\w+)', addr) if res: print(res.groups()) # 根据正则表达式括号部分分组 return res.group(1) else: return False # 测试: assert name_of_email('<Tom Paris> tom@voyager.org') == 'Tom Paris' assert name_of_email('tom@voyager.org') == 'tom' print('ok') - 执行 ('Tom Paris', 'm', 'voyager.org') ('tom', None, 'voyager.org') ok