本章内容:
正则表达式详解(re模块)
1、不使用正则表达式来查找文本的内容
要求从一个字符串中查找电话号码,并判断是否匹配制定的模式,如:555-555-5555。传统的查找方法如下:
def isPhoneNumber(text):
if len(text) != 12:
return False
for i in range(0,3):
if not text[i].isdecimal():
return False
if text[3] !='-':
return False
for i in range(4,7):
if not text[i].isdecimal():
return False
if text[7] !='-':
return False
for i in range(8,12):
if not text[i].isdecimal():
return False
return True
message = "Call me at 415-232-2354 tomorrow. 415-234-2545 is my office."
for i in range(len(message)):
chunk = message[i:i+12]
if isPhoneNumber(chunk):
print('Phone number found:'+chunk)
print('Done')
>>>
Phone number found:415-232-2354
Phone number found:415-234-2545
Done
解析:在for循环的每一次迭代中,取自message的一段新的12个字符被赋值给变量chunk,将chunk传递给isPhoneNumber(),看是否符合电话号码的模式,如果符合就打印出这段文本。最终该循环遍历整个字符串。
2、用正则表达式查找文本模式
正则表达式,简称regex,是文本模式的描述方法。比如 \d\d\d-\d\d\d-\d\d\d\d 可以匹配 3个数字,一个短线,3个数字,一个短线,4个数字,也就是上面说的电话号码的格式。
在一个模式后面加上花括号包围的数字,比如 {3} ,就是说匹配这个模式3次。所以上面的 \d\d\d-\d\d\d-\d\d\d\d 可以表示为 \d{3}-\d{3}-\d{4} 。
2.1 创建正则表达式对象
python中的所有正则表达式的函数都在re模块中,使用前要先导入该模块。
import re
向 re.compile() 传入一个字符串,表示正则表达式,它将返回一个Regex模式对象。
phone_number_regex = re.compile(r'\d{3}-\d{3}-\d{4}')
2.2 匹配Regex对象
Regex对象的search()方法查找传入的字符串,寻找该正则表达式的所有匹配。如果没有找到,则返回None;如果找到了,则返回一个Match对象。Match对象有一个group()方法,它返回被查找字符串中实际匹配的文本。
import re
#正则表达式返回全部文本
phone_number_regex = re.compile(r'\d{3}-\d{3}-\d{4}') #{3}表示这个表达式匹配3次
mo = phone_number_regex.search('My number is 515-555-5555')
print('Phone number found:'+ mo.group()) #mo是一个Match对象,需要调用group()方法才会打印实际匹配到的文本
>>>
Phone number found:515-555-5555
2.3 正则表达式匹配小结
(1)用import re 导入正则表达式模块。
(2)用re.compile() 函数创建一个Regex对象(要使用原始字符串)。
(3)向Regex对象的search()方法传入想要查找的字符串,会返回一个Match对象。
(4)调用Match对象的group()方法,返回实际匹配文本的字符串。
3、用正则表达式匹配更多的模式
3.1 利用括号分组
添加括号将在正则表达式中创建分组没然后可以利用group方法从一个分组中获取匹配的文本。
在正则表达式中,第一对括号是第一组,第二对括号是第二组。向group传入参数1或2,就可以取得匹配到的文本中的不同部分(分组),向group传入参数0或者不传入参数,将会取得匹配到的文本的全部内容。
import re
phone_number_regex = re.compile(r'(\d{3})-(\d{3}-\d{4})')
mo = phone_number_regex.search('My number is 515-555-5555')
print('Phone number found:'+ mo.group(1))
>>>Phone number found:515
如果想要一次获得全部的分组,可以使用groups方法
import re
phone_number_regex = re.compile(r'(\d{3})-(\d{3}-\d{4})')
mo = phone_number_regex.search('My number is 515-555-5555')
g_1 ,g_2 = mo.groups()
print('g_1:',g_1)
print('g_2:',g_2)
>>>
g_1: 515
g_2: 555-5555
因为括号在正则表达式中默认用于分组,所以想要匹配真正的括号时需要对括号进行转义(两边都要转义)
import re
phone_number_regex = re.compile(r'(\(\d{3}\)) (\d{3}-\d{4})')
mo = phone_number_regex.search('My number is (515) 555-5555')
print('Phone number found:'+ mo.group(1))
>>>
Phone number found:(515)
3.2 用管道匹配多个分组
希望匹配多个表达式中的一个时,可以使用管道符 ‘|’ ,如果希望匹配的文本都出现在了被查找的字符串中,那么第一次出现的匹配文本将会作为Match对象被返回。
import re
hero_regex = re.compile(r'Batman|Tina Fey')
mo = hero_regex.search('Batman and Tina Fey')
print(mo.group())
>>>
Batman
也可以通过指定前缀和管道符组合,实现多个表达式的匹配。
import re
bat_regex = re.compile(r'Bat(man|mobile|copter|bat)') #Bat是前缀,与括号中内容进行组合。
mo1 = bat_regex.search('Batmobile lost a wheel')
print(mo1.group()) #返回完全匹配的文本
print(mo1.group(1)) #返回括号分组内匹配的文本
>>>
Batmobile
mobile
3.3 用问号实现可选匹配
字符 ?表示它前面的表达式或分组在这个模式中是可选的。但是?只匹配零次或一次。
import re
bat_Regex = re.compile(r'Bat(wo)?man')
mo_1 = bat_Regex.search('The Adventures of Batman')
mo_2 = bat_Regex.search('The Adventures of Batwoman')
mo_3 = bat_Regex.search('The Adventures of Batwowoman')
print(mo_1.group())
print(mo_2.group())
print(mo_3)
>>>
Batman
Batwoman
None
3.4 用星号 * 匹配零次或多次
星号 * 的匹配方式与 ?有所不同,星号 * 可以匹配多次即只要存在就可以匹配。
import re
bat_Regex = re.compile(r'Bat(wo)*man')
mo_1 = bat_Regex.search('The Adventures of Batwowowoman')
print(mo_1.group())
>>>
Batwowowoman
3.5 用加号 + 匹配一次或多次(至少匹配到一次)
import re
bat_Regex = re.compile(r'Bat(wo)+man')
mo_1 = bat_Regex.search('The Adventures of Batman')
mo_2 = bat_Regex.search('The Adventures of Batwoman')
mo_3 = bat_Regex.search('The Adventures of Batwowoman')
print(mo_1)
print(mo_2.group())
print(mo_3.group())
>>>
None
Batwoman
Batwowoman
3.6 用花括号匹配特定的次数
如果想要一个分组重复特定次数,就在正则表达式中该分组的后面,跟上一个花括号,括号中的数字表示重复的次数。如
phone_number_regex = re.compile(r'\d{3}-\d{3}-\d{4}')
除了制定一个数字,还可以制定一个范围,即在花括号后面写一个最小值a,一个最大值b,如{a,b}。
正则表达式{Ha}{3,5} 将匹配 ‘HaHaHa’、‘HaHaHaHa’ 和 ‘HaHaHaHaHa’ 。也可以省略第一个或者第二个数字,不限定最小值或最大值。
import re
ha_regex_1 = re.compile(r"(ha){3}")
m_1 = ha_regex_1.search('hahahaha')
print(m_1.group())
ha_regex_2 = re.compile(r'(ha){3,5}') #匹配3-5次,返回尽可能多的文本(贪心匹配)
m_2 = ha_regex_2.search('hahahahahaha')
print(m_2.group())
>>>
hahaha
hahahahaha
4、贪心和非贪心匹配
python的正则表达式默认是在有多种匹配结果的情况下,尽可能匹配最长的字符串。如果需要尽可能匹配最短的字符串,在花括号后面添加一个问号 ? 即可
import re
ha_regex_1 = re.compile(r'(ha){3,5}') #匹配3-5次,返回尽可能多的文本(贪心匹配)
m_1 = ha_regex_1.search('hahahahahaha')
print(m_1.group())
ha_regex_2 = re.compile(r'(ha){3,5}?') #匹配3-5次,返回尽可能多的文本(贪心匹配)
m_2 = ha_regex_2.search('hahahahahaha')
print(m_2.group())
>>>
hahahahaha
hahaha
注意: 问号在python中有两种含义:一种是表示匹配可选分组,另一种是声明非贪心匹配,要注意区分。
5、findall() 方法
search方法将返回被查找字符串中第一次匹配到的文本,而findall方法将返回被查找字符串中的匹配到的所有文本,findall返回的不是Match对象,而是一个字符串列表。
import re
phone_numbre_regex = re.compile(r'\d{3}-\d{3}-\d{4}')
phone_number = phone_numbre_regex.findall('Cell:555-555-5555 Work:888-888-8888')
print(phone_number)
>>>
['555-555-5555', '888-888-8888']
如果正则表达式中有分组,则返回元组的列表。
import re
phone_numbre_regex = re.compile(r'(\d{3})-(\d{3}-\d{4})')
phone_number = phone_numbre_regex.findall('Cell:555-555-5555 Work:888-888-8888')
print(phone_number)
>>>
[('555', '555-5555'), ('888', '888-8888')]
6、字符分类
常用字符分类的缩写代码
缩写字符分类 | 表示 |
\d | 0-9的任何数字 |
\D | 除0-9数字以外的任何字符 |
\w | 任何字母、数字、下划线字符(可以认为是匹配‘单词’字符) |
\W | 除字母、数字和下划线以外的任何字符 |
\s | 空格、制表符或换行符(可以认为是匹配‘空白’)字符 |
\S | 除空格、制表符和换行符之外的任何字符 |
7、建立自己的字符分类
可以用方括号 [ ] 建立自己的字符分类,比如[aeiouAEIOU]将匹配所有的元音字母,不论大小写。
import re
vowelregex = re.compile(r'[aeiouAEIOU]')
str_1 = vowelregex.findall('hsoaiejdpoaJEP WGEPAEF JFOPAWJE[oeofjgjop jfgawpe')
print(str_1)
也可以使用短横线表示字母或者数字的范围,比如字符分类[0-9a-zA-Z]将匹配所有的数字和大小写字母。
import re
vowelregex = re.compile(r'[0-9a-zA-Z]')
str_1 = vowelregex.findall('hlsijrg894w4t23\.wsew213^&*%^&$79832hiu we')
print(str_1)
注意在方括号中,普通的正则表达式符号均代表原本的意义,不需要进行转义。
通过在字符分类的左方括号右边添加一个插入字符 ^ ,就可以得到“非字符类”,也就是说正则表达式将匹配不在这个字符分类中的字符。
import re
vowelregex = re.compile(r'[^aeiouAEIOU]')
str_1 = vowelregex.findall('hsoaiej WGEPAEF JFJE[oeoop jfgawpe')
print(str_1)
8、插入字符和美元字符
可以在正则表达式的开始处使用插入字符^,表示匹配必须发生在被查找文本的开始处;
import re
begin = re.compile(r'^hello')
answer_1 = begin.search('hello world')
answer_2 = begin.search('world hello')
print(answer_1.group())
print(answer_2)
>>>
hello
None
同样,也可以在正则表达式的末尾添加一个美元字符$,表示该字符串必须以这个正则表达式的模式结束。
import re
begin = re.compile(r'hello$')
answer_1 = begin.search('hello world')
answer_2 = begin.search('world hello')
print(answer_1)
print(answer_2.group())
>>>
None
hello
可以同时使用插入符和美元字符,表示整个字符串必须匹配该模式,而不是只匹配其中的子集。
import re
begin = re.compile(r'^hello$')
answer_1 = begin.search('hello')
answer_2 = begin.search('world hello')
print(answer_1.group())
print(answer_2)
>>>
hello
None
9、通配字符
句点 . 被称为“通配符” 。它匹配除换行符之外的所有字符。但是句点只能匹配一个字符,比如r'.s' 只能匹配到 ‘us’,而不能匹配‘yours’。
import re
begin = re.compile(r'.')
answer_1 = begin.search('hello')
print(answer_1.group())
>>>
h
9.1 用点-星(.*)匹配所有的字符串,点-星表示任意文本。
import re
begin = re.compile(r'.*')
answer_1 = begin.search('hello')
print(answer_1.group())
>>>
hello
9.2 用句点字符匹配换行
如果想要用点-星匹配包括换行符在内的所有字符,可以通过给compile传入第二个参数re.DOTALL实现。
import re
begin = re.compile(r'.*',re.DOTALL)
answer_1 = begin.search('hello \nworld')
print(answer_1.group())
>>>
hello
world
10、正则表达式常用符号
? | 匹配零次或一次前面的分组 |
* | 匹配零次或多次前面的分组 |
+ | 匹配一次或多次前面的分组 |
{n} | 匹配n次前面的分组 |
{n,} | 匹配n次或更多次前面的分组 |
{,m} | 匹配零次到m次前面的分组 |
{n,m} | 匹配至少n次,至多m次前面的分组 |
{n,m}?或*?或+? | 对前面的分组进行非贪心匹配 |
^spam | 字符串必须以spam开始 |
spam$ | 字符串必须以spam结束 |
. | 匹配除换行符之外的所有字符 |
\d,\w,\s | 分别匹配数字、字母和空格 |
\D,\W,\S | 分别匹配除数字、字母和空格之外的所有字符 |
[abc] | 匹配方括号内的任意字符 |
[^abc] | 匹配不在方括号内的任意字符 |
11、不区分大小写的匹配
一般来说,正则表达式是区分大小写的,如果想要正则表达式不区分大小写,可以向re.compile()中传入re.I作为第二个参数。
import re
begin = re.compile(r'[a-z]*',re.I)
answer_1 = begin.search('ahisdhfliNILSHILHAI')
print(answer_1.group())
>>>
ahisdhfliNILSHILHAI
12、用sub()方法替换字符串
regex对象的sub方法需要传入两个参数,第一个参数是一个字符串,用于取代发现的匹配。第二个参数是一个字符串,是用正则表达式匹配的内容。sub方法返回替换完成后的字符串。
import re
name_regex = re.compile(r'world')
answer_1= name_regex.sub('小姐姐','hello world')
print(answer_1)
>>>
hello 小姐姐
13、管理复杂的正则表达式
通常正则表达式写成一段会很难阅读,所以可以将正则表达式写成多行字符串的形式,用三个引号括起来,同时向re.compile()中传入参数re.VERBOSE 告诉compile 忽略表达式中的空白符和注释。
import re
name_regex = re.compile(r'''
(\d{3}) #匹配三个数字
(.{3}) #匹配除换行符之外的任意内容三次
(\w{3}) #匹配三个字母
''',re.VERBOSE)
answer_1= name_regex.search('555w*whello world')
print(answer_1.group())
>>>
555w*whel
14、组合使用re.I 、re.DOTALL、re.VERBOSE
re.compile()只接受两个参数,如果希望在使用re.I忽略大小写时,同时让句点可以匹配换行符,或者可以在表达式中添加注释,可以用管道符将这些参数合并起来。
regex = re.compile(r'[a-z]',re.I|re.DOTALL|re.VERBOSE)
import re
name_regex = re.compile(r'''
([a-z]*) #匹配任意多个字母,不区分大小写
(\n) #匹配一个换行符
([0-9]*) # 匹配任意多个数字
''',re.I|re.DOTALL|re.VERBOSE)
answer_1= name_regex.search('abcdABCD\n8097809')
print(answer_1.group())
>>>
abcdABCD
8097809