继续第十六章 正则表达式
一、正则表达式特殊字符。我们知道\d代表的是数字字符,也就是从0-9的阿拉伯数字,如果使用管道|的观念,\d相当于是下列正则表达式:(0|1|2|3|4|5|6|7|8|9)。正则表达式的特殊字符说明如下:
import re
msg = 'John, Johnson, Johnnason and Johnnathan will attend my party tonight.'
pattern = '\w+' #不限长度的单词
txt = re.findall(pattern, msg) #英文单词分离
print(txt)
pattern = 'John\w+' #John开头的单词
txt = re.findall(pattern, msg)
print(txt)
msg = '1 cat, 2 dogs, 3 pigs, 4 swans'
#\d+: 不限长度数字 \s: 空格 \w+: 不限长度的数字字母和下划线字符连续字符
pattern = '\d+\s\w+'
txt = re.findall(pattern, msg)
print(txt)
二、字符分类。Python可以使用中括号来设定字符,可参考下列范例。[a-z]:代表a-z的小写字符。[A-Z]:代表A-Z的大写字符。[aeiouAEIOU]:代表英文发音的元音字符。[2-5]:代表2-5的数字。在字符分类中,中括号内可以不用放上正则表示法的反斜杠\执行.、?、*、(、)等字符的转义。例如,[2-5.]会搜寻2-5的数字和句点,这个语法不用写成[2-5\.]。如果在中括号内的左方加上^字符,意义是搜寻不在这些字符内的所有字符
import re
msg = '1. cat, 2. dogs, 3. pigs, 4. swans'
pattern = '[2-5.]' #查找2-5的数字和句点
txt = re.findall(pattern, msg)
print(txt)
pattern = '[^2-5.]' #查找不是2-5的数字和句点的字符
txt = re.findall(pattern, msg)
print(txt)
^字符除了用在中括号内左侧表示反义之外,在正则表达式中起始位置加上^字符,表示正则表示法的字符串必须出现在被查找字符串的起始位置,这样查找成功才算成功。正则表达式的末端放置$字符时,表示正则表达式的字符串必须出现在被查找字符串的最后位置,这样查找成功才算成功。我们也可以将^字符和$字符混合使用,这时如果既要符合开始字符串也要符合结束字符串,所以被搜寻的句子一定要只有一个字符串
import re
msg = 'John will attend my party tonight.'
pattern = '^John' #以John开头的字符
txt = re.findall(pattern, msg)
print(txt)
pattern = '\W$' #结尾字符是非英文字母数字和下划线字符
txt = re.findall(pattern, msg)
print(txt)
msg = 'My best friend is John, He is 28'
pattern = '^John' #以John开头的字符
txt = re.findall(pattern, msg)
print(txt)
pattern = '\d$' #结尾字符是数字字符
txt = re.findall(pattern, msg)
print(txt)
msg = '09238423793'
pattern = '^\d+$' #开头和结尾都是数字的字符串
txt = re.findall(pattern, msg)
print(txt)
msg = '09238ese3793'
pattern = '^\d+$' #开头和结尾都是数字的字符串
txt = re.findall(pattern, msg)
print(txt)
单一字符通配符(wildcard)“.”表示可以查找除了换行字符以外的所有字符(包含空白字符),但是只限定一个字符,如果搜寻的是真正的“.”字符,须使用反斜杠“\.”。
所有字符使用通配符“.*”,它将“.”字符与“*”组合,表示可以查找所有字符,意义是查找0到多个通配符(换行字符除外)。
使用“.*”查找时碰上换行字符,查找就停止。Python的re模块提供参数re.DOTALL,功能是包含查找换行字符,可以将此参数放在search( )、findall( )或compile( )
import re
msg = 'cat hat at matter flat'
pattern = '.at' #单一字符通配符.
txt = re.findall(pattern, msg)
#第3个at符合,Python自动加上空格符。第5个由于只能加上一个字符,所以结果是lat
print(txt)
msg = 'Name: Jiin-Kwei Hung Address: 8F, Nan-Jing E.Rd'
pattern = 'Name:(.*) Address:(.*)' #所有字符通配符.*
txt = re.search(pattern, msg)
name, address = txt.groups()
print("Name: ", name)
print("Address:", address)
msg = 'Name: Jiin-Kwei Hung \nAddress: 8F, Nan-Jing E.Rd'
pattern = '.*'
txt = re.search(pattern, msg) #查找到换行符终止
print(txt.group())
txt = re.search(pattern, msg, re.DOTALL) #查找包含换行符re.DOTALL
print(txt.group())
三、MatchObject对象。之前已经讲解了查找字符串中最重要的2个方法re.search( )和re.findall( )。这里再讲解MatchObject几个重要的方法(method)。首先是re模块的另一个方法re.match( ),这个方法其实和re.search( )相同,差异是re.match( )只查找比对字符串开始的字,如果失败就算失败。re.search( )则是查找整个字符串。至于re.match( )查找成功会传回MatchObject对象,若是查找失败会传回None,这部分与re.search( )相同:
import re
msg1 = 'John will attend my party tonight.'
msg2 = 'My best friend is John.'
pattern = 'John'
txtmatch1 = re.match(pattern, msg1) #msg1中John子串在开头,所以用match查找不到
txtmatch2 = re.match(pattern, msg2) #msg2中John子串不在开头,所以用match查找不到
print(txtmatch1) #打印MatchObject对象
print(txtmatch1.group()) #打印匹配字符串
print(txtmatch2) #打印None
print("查找成功字符串起始索引位置:", txtmatch1.start())
print("查找成功字符串结束索引位置:", txtmatch1.end())
print("查找成功字符串起始结束索引位置:", txtmatch1.span())
txtsearch1 = re.search(pattern, msg1)
txtsearch2 = re.search(pattern, msg2)
print(txtsearch1) #打印MatchObject对象
print(txtsearch1.group()) #打印匹配字符串
print(txtsearch2) #打印MatchObject对象
print(txtsearch2.group()) #打印匹配字符串
print("查找成功字符串起始索引位置:", txtsearch1.start())
print("查找成功字符串结束索引位置:", txtsearch1.end())
print("查找成功字符串起始结束索引位置:", txtsearch1.span())
从上面程序运行结果可知,当使用re.match( )和re.search( )都查找成功时,两者的MatchObject对象内容是相同的。span是注明成功查找字符串的起始位置和结束位置,这里起始索引位置是0,结束索引位置是4。match则是注明成功查找的字符串内容。Python提供下列取得MatchObject对象内容的重要方法:group():返回查找到的字符串。start():返回查找到的字符串的起始位置。 end():返回查找到的字符串的结束位置。span():返回查找到的字符串的起始结束位置。
re模块内的sub( )方法可以用新的字符串取代原本字符串的内容,基本使用语法如下:result = re.sub(pattern, newstr, msg)。pattern是查找的字符串,如果查找成功则用newstr取代,同时成功取代的结果返回给result变量,如果查找到多个相同字符串,这些字符串将全部被取代,需留意原先msg内容将不会改变。如果查找失败则将msg内容返回给result变量,当然msg内容也不会改变,这里举一个用正则表达式re.sub()方法隐藏真实姓名的例子:
import re
msg = 'CIA Mark told CIA Linda that secret USB had given to CIA Peter.'
pattern = r'CIA (\w)\w*' #查找CIA + 空一格后的名字
newstr = r'\1***' #新字符串只保留第一个字母
txt = re.sub(pattern, newstr, msg) #新字符串替代查找字符串
print("替代成功:", txt)
上述程序代码pattern中将查找CIA字符串外加空一格后出现不限长度的字符串(可以由英文大小写或数字或底线所组成)。括号内的(\w)代表必须只有一个字符,同时小括号代表这是一个分组(group),由于整行只有一个括号所以知道这是第一分组,同时只有一个分组,括号外的\w*表示可以有0到多个字符。所以(\w)\w*相当于是1-多个字符组成的单字,同时存在分组1。newstr中的\1代表用分组1找到的第一个字母当作字符串开头,后面***则是接在第一个字母后的字符。对CIA Mark而言所找到的第一个字母是M,所以取代的结果是M***。对CIALinda而言所找到的第一个字母是L,所以取代的结果是L***。对CIA Peter而言所找到的第一个字母是P,所以取代的结果是P***。
四、处理比较复杂的正则表达式。有一个正则表达式内容如下:
pattern = r((\d{3,4}|\(\d{3,4}\))?(\s|-)?\d{8}(\s*(ext|ext.)\s*\d{2,4})?)
我们可以使用3个单引号(或是双引号)将过长的字符串拆成多行表达,当我们适当地拆解后,可以为每一行加上批注,整个正则表达式就变得简单了:
import re
msg = '''020-88223349, (0591)-26669999, 025-29998888 ext 123,
56781234, 027 33887766 ext. 1222'''
pattern = r'''(
(\d{3,4}|\(\d{3,4}\))? #区号
(\s|-)? #区号与电话号码分隔符
\d{8} #电话号码
(\s*(ext|ext.)\s*\d{2,4})? #2-4位的分机号码
)'''
phoneNum = re.findall(pattern, msg, re.VERBOSE)
print(phoneNum)
使用Python时,如果想在正则表达式中加上批注,必须配合使用re.VERBOSE参数,然后将此参数放在search( )、findall( )或compile( ),否则pattern不生效。
下面再看一个电子邮件查找的例子。在字处理过程中,必须在文件内将电子邮件地址解析出来很常见:
import re
msg = '''txt@deeplearning.com.cn bsdfas@gmail.com'''
pattern = r'''(
[a-zA-Z0-9_.]+ #邮箱账号
@ #区号与电话号码分隔符
[a-zA-Z0-9-.]+ #主机域名domain
[\.] #.符号
[a-zA-Z]{2,4} #可能是com或edu或其它
([\.])? #.符号,也可能无,例如美国
([a-zA-Z]{2,4})? #国别简称,也可能无
)'''
phoneNum = re.findall(pattern, msg, re.VERBOSE)
print(phoneNum)
如果我们想要一次放置多个参数特性,方法是使用管道|概念,例如,可以使用下列方式:datastr = re.search(pattern, msg, re.IGNORECASE|re.DOTALL|re.VERBOSE)