引言
如果我们希望在字符串里面去寻找电话号码,电话号码的模式一般是这样的:3个数字+一个短横线+3个数字+一个短横线+4个数字。比如415-555-4242
如果我们希望通过一个函数来实现检查字符串是否匹配模式,它会返回True或者False,代码如下:
def isPhoneNumber(text): if len(text)!=12: return False #isdigit()函数用来判断一个字符串是否全是数字 for i in range(0,12): if i==3 or i==7: if text[i] != '-': return False else: if not text[i].isdigit(): return False return True for i in range(3): s=input("请输入要匹配的电话号码:") if isPhoneNumber(s): print("{}是合法的".format(s)) else: print("{}是不合法的".format(s))
运行是这样的:
请输入要匹配的电话号码:123-456-7890 123-456-7890是合法的 请输入要匹配的电话号码:12-354678-199 12-354678-199是不合法的 请输入要匹配的电话号码:1234665758889-0 1234665758889-0是不合法的
正则表达式查找文本模版
前言
前面的电话号码查找程序能工作,但是它只能实现对一种电话号码模式的匹配,象123.456.7890/(123)456-7890也是合理的电话号码模式,当我们对它们进行匹配的时候就要去增加逻辑,但是有没有更简单的方式呢?这就是我们今天要提到的正则表达式
常见的匹配字符
- #### . 通配符,匹配任意1个字符(除了\n换行)
[ ] 匹配[ ]中列举的字符,里面的元素之间存在“或”的关系,[abc]只能匹配a或b或c
\d 匹配数字,等价于[0,9]
\D 匹配非数字,等价于[^0-9]
\s 匹配空白,常见的空白符的形式有空格、制表符 (\t)、换行 (\n) 和回车 (\r)
\S 匹配非空白
\w 匹配非特殊字符,即a-z、A-Z、0-9、_、中文
\W 匹配特殊字符
创建正则表达式对象
python里面有关于正则表达式的函数都在re模块,它在我们安装的python环境中,所以我们只需要导入即可:
import re
我们可以向re.complie()中传入一个字符串,表示正则表达式,它将返回一个Regex模式对象,例如如果要创建一个Regex对象来匹配电话号码,只需要:
phoneNumber=re.compile(r'\d\d\d-\d\d\d-\d\d\d\d')
匹配Regex对象
Regex对象的searc()方法查找输入的字符串,以寻找该正则表达式的所有匹配。如果未找到,则返回None,如果找到了该模式,search将返回一个Match对象,而它里面又一个**group()**方法返回所有实际上匹配的文本:
import re phoneNumber=re.compile(r'\d\d\d-\d\d\d-\d\d\d\d') mo=phoneNumber.search("My name is 147-907-7916") print("The phone number:{}".format(mo.group()))
输出为:
The phone number:147-907-7916
利用正则表达式实现匹配更多模式
上面我们介绍了python创建和查找正则表达式对象的基本步骤,现在让我们来尝试一些更加强大的匹配模式
利用括号分组
假如我们想把电话号码的区号从代码里面分离,可以添加括号在代码里面添加分组然后从分组里面获得匹配文本。
正则表达式的第几组括号就是第几组,向group输入n就可以获得第n组获得的文本信息,而传0或不传就是获得全部的匹配文本,示例如下:
phonenumber2=re.compile(r'(\d\d\d)-(\d\d\d\d-\d\d\d\d)') mo=phonenumber2.search("My phoneNumber is 147-9037-7916") areaCode,mainNumber=mo.groups() print("区号:{},电话号码:{},完整号码:{}".format(areaCode,mainNumber,mo.group()))
输出为:
区号:147,电话号码:9037-7916,完整号码:147-9037-7916
注意:括号在正则表达式里面有特殊的含义,如果我们在匹配的时候想匹配括号,我们可以使用\符号对括号进行字符转义,示例如下:
phonenumber2=re.compile(r'(\d\d\d)-(\(\d\d\d\d-\d\d\d\d\))') mo=phonenumber2.search("My phoneNumber is 147-(9037-7916)") areaCode,mainNumber=mo.groups() print("区号:{},电话号码:{},完整号码:{}".format(areaCode,mainNumber,mo.group()))
输出为:
区号:147,电话号码:(9037-7916),完整号码:147-(9037-7916)
而在后面我们会遇到其他有特殊含义的字符,如果我们在匹配的时候想匹配它们,我们可以也使用\符号对它们进行字符转义。
用管道匹配多个分组
我们将字符**|**称为“管道”,当我们希望匹配许多表达式中的一个就可以使用它,如下:
heroRegex1=re.compile(r'\d\d\d-\d\d-\d\d|\d\d\d\d-\d\d') mo1=heroRegex1.search("456-78-90 9876-54 3456-78 456-78 12-3456-78 987-6-54 123-45-67 1234-56 123-45-678 987-12-34") print(mo1.group())
输出结果为:
456-78-90
我们尝试交换匹配文本的数据:
heroRegex2=re.compile(r'\d\d\d\d-\d\d|\d\d\d-\d\d-\d\d') mo2=heroRegex2.search("9876-54 456-78-90 3456-78 456-78 12-3456-78 987-6-54 123-45-67 1234-56 123-45-678 987-12-34") print(mo2.group())
输出结果为:
9876-54
我们也可以将它与上面说的括号混合使用:
batRegex=re.compile(r'Bat(man|mobile|copter|bat)') mo=batRegex.search("Batmobile lost a wheel") print(mo.group()) print(mo.group(1))
运行结果:
Batmobile mobile
上面就实现对多个拥有相同的前缀如何只指定一次前缀来指定几种可选的模式来让正则表达式来匹配。
用问号实现可选匹配
有时候,一些文本我们在匹配的时候希望它存在,但没有其实也可以接受,这时候我们可以使用字符**?**来表示前面的分组在该模式中是可选的,如下面的这一串代码:
answerFinder=re.compile(r'Bat(wo)?man') mo=answerFinder.search("The Batman") mo1=answerFinder.search("The Batwoman") print("the mo:{}\nthe mo1:{}".format(mo.group(),mo1.group()))
输出为:
the mo:Batman the mo1:Batwoman
如上**(wo)?表示wo是可选,无论是Batman还是Batwoman**都可以匹配成功
利用*匹配零次或多次
表示匹配0次或多次,即****前面的分组可以出现任意次,示例;
answerFinder=re.compile(r'Bat(wo)*man') mo=answerFinder.search("The Batman") mo1=answerFinder.search("The Batwowowowowoman") print("the mo:{}\nthe mo1:{}".format(mo.group(),mo1.group()))
输出:
the mo:Batman the mo1:Batwowowowowoman
利用加号匹配一次或多次
相比较于的0次或多次,+则是要求分组必须要出现一次,其余与基本一致。
利用{}匹配特定次数
如果想要一个分组重复指定次数,就在正则表达式中该分组后面跟上**{}**包围的数字。例如(\d){4}表示将匹配四个整数
除了在**{}**填入一个数字以外,还可以指定一个范围:
# 匹配两次 answerFinder=re.compile(r'Bat(wo){2}man') #匹配至少两次 answerFinder=re.compile(r'Bat(wo){2,}man') #匹配至多两次 answerFinder=re.compile(r'Bat(wo){,2}man')
示例:
phoneNumber1=re.compile(r'(\d){3}-(\d){3}-(\d){4}') mo=phoneNumber1.search("My name is 147-907-7916") print("The phone number:{}".format(mo.group()))
输出为:
The phone number:147-907-7916
贪心与非贪心匹配
如果我们匹配字符串**”hahahahaha“** (ha{3,5)可以匹配3个,4个或5个实例,但是当我们去匹配的时候就会发现只会返回hahahahaha,这是因为python的正则表达式默认是**”贪心“的,这表示有多个结果的时候,会默认选择尽可能长的字符串,但我们也可以在{}后面加上?**让其变为非贪心模式,示例如下:
answerFinder1=re.compile(r'(ha){3,5}') answerFinder2=re.compile(r'(ha){3,5}?') mo1=answerFinder1.search("hahahahaha") mo2=answerFinder2.search("hahahahaha") print("the mo1:{}\nthe mo2:{}".format(mo1.group(),mo2.group()))
输出为:
the mo1:hahahahaha the mo2:hahaha
findall()方法
除了search()方法,Regex对象还有一个findall()方法,search会返回一个Match对象,包含被查找字符串中”第一次“匹配的文本;而findall()方法将返回一组字符串,包含被查找字符串里面所有匹配文本,而且它返回的不是一个Match对象,而是一个字符串列表(如果在正则表达式中没有分组),如果有分组,那返回的就是一个元组的列表,其中的项就是正则表达式中每个分组的匹配字符串,如下:
heroRegex2=re.compile(r'\d\d\d\d-\d\d') mo2=heroRegex2.findall("9876-54 456-78-90 3456-78 456-78 12-3456-78 987-6-54 123-45-67 1234-56 123-45-678 987-12-34") print(mo2) heroRegex2=re.compile(r'(\d\d\d\d)-(\d\d)') mo1=heroRegex2.findall("9876-54 456-78-90 3456-78 456-78 12-3456-78 987-6-54 123-45-67 1234-56 123-45-678 987-12-34") print(mo1)
输出为:
['9876-54', '3456-78', '3456-78', '1234-56'] [('9876', '54'), ('3456', '78'), ('3456', '78'), ('1234', '56')]
创建自己的字符分类
在前面我们介绍了常见的匹配字符,但是我们不得不承认这些字符的范围太过宽泛,我们可以使用方括号来定义自己的字符分类,例如我们可以使用**[aoeiuAOEIU]**来匹配所有的元音字符 ,
vowelRegx=re.compile(r'[aeiouAEIOU]') mo=vowelRegx.findall("The quick brown fox jumped over the lazy dog") print(mo)
输出:
['e', 'u', 'i', 'o', 'o', 'u', 'e', 'o', 'e', 'e', 'a', 'o']
插入字符^与美元字符$
我们可以在正则表达式开始处使用^,表示匹配必须发生在被查找文本开始处。同样的,我们可以在正则表达式的末尾加上, 表示必须以这个正则表达式的模式结束,我们可以同时使 用 和 ,表示必须以这个正则表达式的模式结束,我们可以同时使用^和,表示必须以这个正则表达式的模式结束,我们可以同时使用和,表示整个字符串必须匹配该模式,而不是字符串的某个子集,示例如下:
beginwithHello=re.compile(r'^Hello') mo1=beginwithHello.search("Hello,World") mo2=beginwithHello.search("Python,Hello") print("the mo1:{}\nthe mo2:{}".format(mo1.group(),mo2.group())) endwithHello=re.compile(r'Hello$') mo1=endwithHello.search("Hello,World") mo2=endwithHello.search("Python,Hello") print("the mo1:{}\nthe mo2:{}".format(mo1.group(),mo2.group()))
我们会发现会报错
Traceback (most recent call last): File "E:\python\typora源码放置处(python)\正则表达式.py", line 76, in <module> print("the mo1:{}\nthe mo2:{}".format(mo1.group(),mo2.group())) ^^^^^^^^^ AttributeError: 'NoneType' object has no attribute 'group'
修改一下
beginwithHello = re.compile(r'^Hello') mo1 = beginwithHello.search("Hello,World") mo2 = beginwithHello.search("Python,Hello") print("the mo1:{}\n".format(mo1.group())) endwithHello = re.compile(r'Hello$') mo2 = endwithHello.search("Python,Hello") print("the mo2:{}\n".format(mo2.group()))
输出为
the mo1:Hello the mo2:Hello
通配字符
在正则表达式中,**.**字符称为“通配字符”它可以匹配除了换行符以外的所有字符。
利用点-星匹配所有字符
有时候想要匹配所有字符串,例如假定想要匹配字符串First Name,接下来是任意文本,然后Last Name,然后还是任意文本,示例:
nameRegex=re.compile(r'First Name:(.*) Last Name:(.*)') mo=nameRegex.search("First Name:John Last Name:Doe") print(mo.group(1)) print(mo.group(2))
输出为:
John Doe
非贪心匹配
nonggreedyRegex=re.compile(r'<.*?>') mo=nonggreedyRegex.search("<To be, or not to be> is a question") print(mo.group())
<To be, or not to be>
用句号字符匹配换行符
点-星可以匹配换行符外的所有字符,当我们传入re.DOTALL作为**re.compile()**的第二个参数,继而让句点字符匹配所有字符,包括换行符,示例如下:
nonggreedyRegex1=re.compile(r'.*') mo1=nonggreedyRegex1.search("To be, or not to be> is a question\nFirst Name:John Last Name:Doe\nThe quick brown fox jumped over the lazy dog") print(mo1.group()) nonggreedyRegex2=re.compile(r'.*') mo1=nonggreedyRegex2.search("To be, or not to be> is a question\nFirst Name:John Last Name:Doe\nThe quick brown fox jumped over the lazy dog") print(mo2.group())
输出:
To be, or not to be is a question To be, or not to be is a question First Name:John Last Name:Doe The quick brown fox jumped over the lazy dog
不区分大小
通常,正则表达式在匹配的时候会注意文本的大小写,而不关心它们的大小写的话,可以传入re.I或者re,IGNOREVASE作为**re.compile()**的第二个参数,示例:
robocup=re.compile(r'Robocup',re.I) mo1=robocup.search("Robocup is a sport") mo2=robocup.search("ROBOCUP is a sport") mo3=robocup.search("ROboCUp is a sport") print("{},{},{}".format(mo1.group(),mo2.group(),mo3.group()))
输出为:
Robocup,ROBOCUP,ROboCUp
用sub()方法替换字符串
正则表达式不仅可以找到文本模式,而且还能用新的文本替换掉这些模式,Regex对象的sub方法需要接受两个参数:
- 一个用来替换发现的匹配的字符串
- 正则表达式
示例:
nameRegex = re.compile(r'Agent\w+') nameRegex.sub("fengxu", "Agent Alice gave the secret documents to Agent Bob")
有时候我们需要匹配文本表示作为替换字符串的一部分,在sub方法的第一个参数中,可以输入**\1,\2,\3表示在替换中输入分组1、2、3**的文本,示例:
agentNameRegex=re.compile(r'Agent (\w)\w*') result=agentNameRegex.sub(r'\1****','Agent Alice told Agent Carol that Agent Eve knew Agent Bob was double agent') print(result)
输出为:
A**** told C**** that E**** knew B**** was double agent
管理复杂的正则表达式
如果匹配的文本模式很简单,那么使用简单的正则表达式就足够了,但是匹配复杂的文本模式 ,正则表达式可能很长很复杂,这时候我们就要引用空白符与注释了,这时候我们可以输入re.VERBOSE作为第二个参数,如下:
phoneRegex=re.compile(r'((\d{3}|\(\d{3}\))?(\s|-|\.)?\d{3}(\s|-|\.)\d{4}(\S*(ext|x|ext.)\s*\d{2.5})?)') phoneRegex2=re.compile('''( (\d{3}|\(\d{3}\))? #area code (\s|-|\.)? #separator \d{3} #first 3 digits (\s|-|\.) #separator \d{4} # last digits (\S*(ext|x|ext.)\s*\d{2.5})?# extension )''',re.VERBOSE)