《Python编程实训快速上手》第五天--模式匹配与正则表达式
一、不用正则表达式查找文本模式
文本模式是一种人为规定的结构,现在有一个模式:3个数字-3个数字-4个数字
使用isPhoneNumber()函数来判断字符串是否匹配该模式
def isPhoneNumber(number):if len(number) != 12:return Falsefor i in range(0,3):if not number[i].isdecimal():return Falseif number[3] != "-":return Falsefor i in range(4,7):if not number[i].isdecimal():return Falseif number[7] != "-":return Falsefor i in range(8,12):if not number[i].isdecimal():return Falsereturn Trueprint(isPhoneNumber(""))
print(isPhoneNumber("112-564-7896"))
print(isPhoneNumber("112-7896-532"))
上述代码缺陷在于它仅能处理一种模式,新增一个模式后,就要增加新的代码
如果想在更长的字符串中寻找电话号码,就必须添加更多代码来寻找电话号码模式:
message = "Call me at 415-555-1011 tomorrow, 415-555-9865 is my office"
for i in range(len(message)):chunk = message[i:i+12]if isPhoneNumber(chunk):print("Phone number %s is found"%chunk)
print("done")
总结上述代码发现,如果在一个长度为百万的字符串中匹配模式,那么效率是十分低下的
二、使用正则表达式查找文本模式
1、创建正则表达式对象
1)正则表达式的函数均在re模块中,导入即可:import re
2)创建Regex模式对象:phoneNumRegex = re.compile(r"\d\d\d-\d\d\d-\d\d\d\d")
\d在正则表达式中表示一位数字字符,即0~9的数字
2、匹配Regex对象
整体代码如下:
import re
phoneNumRegex = re.compile(r"\d{3}-\d{3}-\d{4}")
mo = phoneNumRegex.search("My number is 123-456-7890")
print("My phone number is found: " + mo.group())
首先,对象使用search方法查找传入的字符串,寻找正则表达式的所有匹配。如果未找到正则表达式模式,则返回None。如果找到了,则返回一个Match对象,使用Match对象中的group()方法,返回查找字符串中实际匹配的文本
3、正则表达式过程匹配回顾
- import re
- re.compile()函数创建Regex对象
- 使用search()方法 返回Match对象
- 使用group()方法返回实际匹配文本的字符串
三、用正则表达式匹配更多模式
1、利用()分组
现在想将\d\d\d-\d\d\d-\d\d\d\d分成两组即\d\d\d和\d\d\d-\d\d\d\d,做法是(\d\d\d)-(\d\d\d-\d\d\d\d)
上边提到使用group()方法进行匹配字符串返回 ,因此操作如下:
mo.group()---->"123-456-7890"
mo.group(0)---->"123-456-7890"
mo.group(1)---->"123"
mo.group(2)---->"456-7890"
mo.groups()---->("123","456-7890")
group中的数字表示第几个分组
前边使用compile()方法时曾使用r进行原始字符串的转换, 从而消除\d中\的转义作用。
在正则表达式中,也存在一些特殊字符:. ^ $ * + ? { } [ ] \ | ( )
如果模式中刚好出现上述特殊字符,则需要用\进行转义操作,例如:r"(\(\d\))"
2、用管道匹配多个分组
字符 | 表示管道,r"a|b"表示匹配"a" 或"b",即若先遇到a,则返回a;先遇到b,则返回b
batRegex = re.compile(r"Batman|computer")
mo = batRegex.search("Batman has a computer")
print(mo.group())
mo = batRegex.search("A computer was bought by Batman")
print(mo.group())
也可匹配多个模式中的一个,允许提取公共项方式,假设希望匹配Batman、Batmobile、Batcopter和Batbar中的任意一个,则代码如下
batRegex = re.compile(r"Bat(man|mobile|copter|bar)")
mo = batRegex.search("Batman is rich")
print(mo.group())
print(mo.group(1))
3、用?实现可选择匹配
字符?表明它前边的分组在这个模式中是可选的,即无论这段文本在不在,正则表达式都会认为匹配。利用前边电话例子,寻找包含区号或不包含区号的电话号码:
phoneNumRegex = re.compile(r"(\d{3}-)?\d{3}-\d{4}")
mo = phoneNumRegex.search("My number is 123-456-7890")
print("My phone number is found: " + mo.group())
mo = phoneNumRegex.search("My number is 456-7890")
print("My phone number is found: " + mo.group())
4、用*匹配0次或多次
*前的分组可以在文本中出现任意次(包括0次)
phoneNumRegex = re.compile(r"(\d{3}-)*\d{3}-\d{4}")
mo = phoneNumRegex.search("My number is 456-7890")
print("My phone number is found: " + mo.group())
mo = phoneNumRegex.search("My number is 123-456-7890")
print("My phone number is found: " + mo.group())
mo = phoneNumRegex.search("My number is 123-456-223-7890")
print("My phone number is found: " + mo.group())
5、用+匹配1次或多次
+前的分组必须“至少出现1次”
phoneNumRegex = re.compile(r"(\d{3}-)+\d{3}-\d{4}")
mo = phoneNumRegex.search("My number is 456-7890")
if mo is None:print("My phone number is not found")
mo = phoneNumRegex.search("My number is 123-456-7890")
print("My phone number is found: " + mo.group())
mo = phoneNumRegex.search("My number is 123-456-223-7890")
print("My phone number is found: " + mo.group())
6、用{}匹配特定次数
\d{3}表示让\d重复3次,即\d\d\d
1)指定循环次数:将一个分组重复特定次数,就在该分组后跟上{数字}
2)指定范围:
(ha){1,3}将匹配"ha"、"haha"、"hahaha"
(ha){1,}表示不限定最大值
(ha){,5}表示匹配0~5次
四、贪心和非贪心匹配
Python的正则表达式默认是“贪心的”,在有二义性的情况下,会尽可能匹配最长的字符串,例如(ha){1,3}去查找"hahaha"时只会返回"hahaha"。
{}?表示“非贪心”版本,即会尽可能匹配最短的字符串
greedyRegex = re.compile(r"(Ha){3,5}")
mo1 = greedyRegex.search("HaHaHaHaHa")
print(mo1.group())unGreedyRegex = re.compile(r"(Ha){3,5}?")
mo2 = unGreedyRegex.search("HaHaHaHaHa")
print(mo2.group())
五、findall()方法
search()方法只返回一个match对象,包含被查找字符串中的“第一次”匹配的文本
findall()方法将返回一组字符串列表, 包含被查找字符串中的“所有”匹配的文本
findall()方法返回结果总结:
- 没有分组,将返回一个匹配字符串的列表
- 有分组,返回一个字符串的元组的列表,每个分组对应一个字符串
1)没有分组:
phoneNumRegex = re.compile(r'\d\d\d-\d\d\d-\d\d\d\d')
print(phoneNumRegex.findall("Cell: 415-856-7854 work:235-000-7459"))
2)有分组
phoneNumRegex = re.compile(r'(\d\d\d)-(\d\d\d)-(\d\d\d\d)')
print(phoneNumRegex.findall("Cell: 415-856-7854 work:235-000-7459"))
六、字符分类
\d | 0~9的任意数字 |
\D | 除0~9数字外的任何字符 |
\w | 任何字母、数字或下划线字符(匹配“单词”字符) |
\W | 除字母、数字和下划线以外的任何字符 |
\s | 空格、制表符或换行符(匹配“空白”字符) |
\S | 除空格、制表符或换行符外的任何字符 |
\d+\s\w+表示匹配的文本有一个或多个数字( \d+),然后是一个空白字符(\s),接下来是一个或多个字母/数字/下划线字符(\w+)
七、建立自己的字符分类
使用[]定义自己的字符分类,在[]内普通的正则表达式符号不会被解释,因此无需加\进行转义
在做方括号后加上一个^字符,即可得到“非字符类”,即不在这个字符类中的所有字符
phoneNumRegex = re.compile(r'[aeiouAEIOU]')
print(phoneNumRegex.findall("Bittersweet as life is, It's still wonderful, and It's fascinating even in tragedy"))
phoneNumRegex = re.compile(r'[^aeiouAEIOU]')
print(phoneNumRegex.findall("Bittersweet as life is, It's still wonderful, and It's fascinating even in tragedy"))
可使用-表示字母或数字范围:[a-zA-z0-9]将匹配所有大小写字母和数字
八、插入字符和美元符号
在正则表达式开始处使用^符号,表示匹配必须发生在被查找文本开始处
在正则表达式末尾使用$符号,表示该字符串必须以这个正则表达式的模式结束
同时使用^和$符号,表示整个字符串必须匹配该模式
如果字符串违背上述规定,则查找结果返回None
九、通配字符
.表示通配字符,可以匹配除换行符以外的所有字符,一个.只能匹配一个字符
1、用.*匹配所有字符
默认为贪心模式,如果想使用非贪心模式,则用.*?
nameRegex = re.compile(r"<.*?>")
mo = nameRegex.search("<to serve man> for dinner>")
print(mo.group())nameRegex2 = re.compile(r"<.*>")
mo = nameRegex2.search("<to serve man> for dinner>")
print(mo.group())
2、 句点字符匹配换行符
向re.compile()中传入re.DOTALL参数,即可让.匹配所有字符
- 如果没有re.DOTALL参数,.将匹配所有字符直到第一个\n出现
- 如果有该参数,将匹配所有字符
nameRegex = re.compile(r".*")
mo = nameRegex.search("serve the public trust. \nProtect the innocent. \nUphold the law")
print(mo.group())print("----------------------")nameRegex2 = re.compile(r".*",re.DOTALL)
mo = nameRegex2.search("serve the public trust. \nProtect the innocent. \nUphold the law")
print(mo.group())
十、正则表达式符号匹配回顾
? | 匹配0次或1次前边的分组 |
* | 匹配0次或多次前边的分组 |
+ | 匹配1次或多次前边的分组 |
{n} | 匹配n次前边的分组 |
{n,} | 匹配n次或更多次前边的分组 |
{,m} | 匹配0次到m次前边的分组 |
{n,m} | 匹配至少n次,至多m次前边的分组 |
{n,m}? *? +? | 对前边的分组进行非贪心匹配 |
^spam | 字符换必须以spam开始 |
spam$ | 字符串必须以spam结束 |
. | 匹配所有字符,换行符除外 |
\d,\w,\s | 分别匹配数字,单词和空格 |
\D,\W,\S | 分别匹配数字、单词和空格外的所有字符 |
[abc] | 匹配方括号内的任意字符 |
[^abc] | 匹配不在方括号内的任意字符 |
十一、不区分大小写匹配
向re.compile()中传入re.IGNORECASE或re.I
十二、用sub()方法替换字符串
sub()方法实现找到文本模式后,用新的文本替换掉这些模式
sub(参数1,参数2)
- 参数1:一个字符串,用于替换发现的匹配
- 参数2:一个字符串,即正则表达式
sub返回替换完成的字符串
namesRegex = re.compile(r"Agent \w+")
print(namesRegex.sub("censored","Agent Alice gave the secret documents to Agent Bob"))
当使用匹配的文本本身作为替换的一部分时,在第一个参数中输入\1,\2,\3表示“在替换中输入分组1,2,3的文本
namesRegex = re.compile(r"Agent (\w)\w*")
print(namesRegex.sub(r"\1***","Agent Alice gave the secret documents to Agent Bob"))
理解:使用\1***替换匹配到的文本内容,\1表示显示分组1内容
十三、管理复杂的正则表达式
如果正则表达式太长,想要分行写,则向compile中的第二个参数传入re.VERBOSE,表示忽视正则表达式字符串中的空白符和注释
phoneRegex = re.compile(r"""((\d{3} | \(\d{3}\))? #area code(\s|-|\.)? #separator\d{3} #first 3 digits(\s|-|\.) #separator\d{4} #last 4 digits(\s*(ext|x|ext.)\s*\d{2,5})? #extension)""",re.VERBOSE)
十四、组合使用compile中的第二个参数
使用|方法进行连接
someRegexValue = re.compile(r"foo",re.IGNORECASE | re.DOTALL | re.VERBOSE)